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 node group (step down into tree)
1299 return getFirstValidTreeInfoEntry(node->node_group);
1301 if (node->parent_link) // skip first node (back link) of node group
1303 // get next regular tree node, or step up until one is found
1304 while (node->next == NULL && node->node_parent != NULL)
1305 node = node->node_parent;
1307 return getFirstValidTreeInfoEntry(node->next);
1310 // this is a regular tree node
1314 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1319 if (node->node_parent == NULL) // top level group
1320 return *node->node_top;
1321 else // sub level group
1322 return node->node_parent->node_group;
1325 int numTreeInfoInGroup(TreeInfo *node)
1327 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1330 int getPosFromTreeInfo(TreeInfo *node)
1332 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1337 if (node_cmp == node)
1341 node_cmp = node_cmp->next;
1347 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1349 TreeInfo *node_default = node;
1361 return node_default;
1364 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1365 int node_type_wanted)
1367 if (identifier == NULL)
1372 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1373 strEqual(identifier, node->identifier))
1376 if (node->node_group)
1378 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1391 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1393 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1396 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1397 TreeInfo *node, boolean skip_sets_without_levels)
1404 if (!node->parent_link && !node->level_group &&
1405 skip_sets_without_levels && node->levels == 0)
1406 return cloneTreeNode(node_top, node_parent, node->next,
1407 skip_sets_without_levels);
1409 node_new = getTreeInfoCopy(node); // copy complete node
1411 node_new->node_top = node_top; // correct top node link
1412 node_new->node_parent = node_parent; // correct parent node link
1414 if (node->level_group)
1415 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1416 skip_sets_without_levels);
1418 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1419 skip_sets_without_levels);
1424 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1426 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1428 *ti_new = ti_cloned;
1431 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1433 boolean settings_changed = FALSE;
1437 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1438 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1439 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1440 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1441 char *graphics_set = NULL;
1443 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1444 graphics_set = node->graphics_set_ecs;
1446 if (node->graphics_set_aga && (want_aga || has_only_aga))
1447 graphics_set = node->graphics_set_aga;
1449 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1451 setString(&node->graphics_set, graphics_set);
1452 settings_changed = TRUE;
1455 if (node->node_group != NULL)
1456 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1461 return settings_changed;
1464 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1466 boolean settings_changed = FALSE;
1470 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1471 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1472 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1473 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1474 char *sounds_set = NULL;
1476 if (node->sounds_set_default && (want_default || has_only_default))
1477 sounds_set = node->sounds_set_default;
1479 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1480 sounds_set = node->sounds_set_lowpass;
1482 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1484 setString(&node->sounds_set, sounds_set);
1485 settings_changed = TRUE;
1488 if (node->node_group != NULL)
1489 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1494 return settings_changed;
1497 int dumpTreeInfo(TreeInfo *node, int depth)
1499 char bullet_list[] = { '-', '*', 'o' };
1500 int num_leaf_nodes = 0;
1504 Debug("tree", "Dumping TreeInfo:");
1508 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1510 for (i = 0; i < depth * 2; i++)
1511 DebugContinued("", " ");
1513 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1514 bullet, node->name, node->identifier,
1515 (node->node_parent ? node->node_parent->identifier : "-"),
1516 (node->node_group ? "[GROUP]" : ""));
1518 if (!node->node_group && !node->parent_link)
1522 // use for dumping artwork info tree
1523 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1524 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1527 if (node->node_group != NULL)
1528 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1534 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1536 return num_leaf_nodes;
1539 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1540 int (*compare_function)(const void *,
1543 int num_nodes = numTreeInfo(*node_first);
1544 TreeInfo **sort_array;
1545 TreeInfo *node = *node_first;
1551 // allocate array for sorting structure pointers
1552 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1554 // writing structure pointers to sorting array
1555 while (i < num_nodes && node) // double boundary check...
1557 sort_array[i] = node;
1563 // sorting the structure pointers in the sorting array
1564 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1567 // update the linkage of list elements with the sorted node array
1568 for (i = 0; i < num_nodes - 1; i++)
1569 sort_array[i]->next = sort_array[i + 1];
1570 sort_array[num_nodes - 1]->next = NULL;
1572 // update the linkage of the main list anchor pointer
1573 *node_first = sort_array[0];
1577 // now recursively sort the level group structures
1581 if (node->node_group != NULL)
1582 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1588 void sortTreeInfo(TreeInfo **node_first)
1590 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1594 // ============================================================================
1595 // some stuff from "files.c"
1596 // ============================================================================
1598 #if defined(PLATFORM_WIN32)
1600 #define S_IRGRP S_IRUSR
1603 #define S_IROTH S_IRUSR
1606 #define S_IWGRP S_IWUSR
1609 #define S_IWOTH S_IWUSR
1612 #define S_IXGRP S_IXUSR
1615 #define S_IXOTH S_IXUSR
1618 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1623 #endif // PLATFORM_WIN32
1625 // file permissions for newly written files
1626 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1627 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1628 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1630 #define MODE_W_PRIVATE (S_IWUSR)
1631 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1632 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1634 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1635 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1636 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1638 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1639 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1640 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1643 char *getHomeDir(void)
1645 static char *dir = NULL;
1647 #if defined(PLATFORM_WIN32)
1650 dir = checked_malloc(MAX_PATH + 1);
1652 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1655 #elif defined(PLATFORM_EMSCRIPTEN)
1656 dir = "/persistent";
1657 #elif defined(PLATFORM_UNIX)
1660 if ((dir = getenv("HOME")) == NULL)
1662 dir = getUnixHomeDir();
1665 dir = getStringCopy(dir);
1677 char *getPersonalDataDir(void)
1679 static char *personal_data_dir = NULL;
1681 #if defined(PLATFORM_MACOSX)
1682 if (personal_data_dir == NULL)
1683 personal_data_dir = getPath2(getHomeDir(), "Documents");
1685 if (personal_data_dir == NULL)
1686 personal_data_dir = getHomeDir();
1689 return personal_data_dir;
1692 char *getMainUserGameDataDir(void)
1694 static char *main_user_data_dir = NULL;
1696 #if defined(PLATFORM_ANDROID)
1697 if (main_user_data_dir == NULL)
1698 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1699 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1700 SDL_AndroidGetExternalStoragePath() :
1701 SDL_AndroidGetInternalStoragePath());
1703 if (main_user_data_dir == NULL)
1704 main_user_data_dir = getPath2(getPersonalDataDir(),
1705 program.userdata_subdir);
1708 return main_user_data_dir;
1711 char *getUserGameDataDir(void)
1714 return getMainUserGameDataDir();
1716 return getUserDir(user.nr);
1719 char *getSetupDir(void)
1721 return getUserGameDataDir();
1724 static mode_t posix_umask(mode_t mask)
1726 #if defined(PLATFORM_UNIX)
1733 static int posix_mkdir(const char *pathname, mode_t mode)
1735 #if defined(PLATFORM_WIN32)
1736 return mkdir(pathname);
1738 return mkdir(pathname, mode);
1742 static boolean posix_process_running_setgid(void)
1744 #if defined(PLATFORM_UNIX)
1745 return (getgid() != getegid());
1751 void createDirectory(char *dir, char *text, int permission_class)
1753 if (directoryExists(dir))
1756 // leave "other" permissions in umask untouched, but ensure group parts
1757 // of USERDATA_DIR_MODE are not masked
1758 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1759 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1760 mode_t last_umask = posix_umask(0);
1761 mode_t group_umask = ~(dir_mode & S_IRWXG);
1762 int running_setgid = posix_process_running_setgid();
1764 if (permission_class == PERMS_PUBLIC)
1766 // if we're setgid, protect files against "other"
1767 // else keep umask(0) to make the dir world-writable
1770 posix_umask(last_umask & group_umask);
1772 dir_mode = DIR_PERMS_PUBLIC_ALL;
1775 if (posix_mkdir(dir, dir_mode) != 0)
1776 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1778 if (permission_class == PERMS_PUBLIC && !running_setgid)
1779 chmod(dir, dir_mode);
1781 posix_umask(last_umask); // restore previous umask
1784 void InitMainUserDataDirectory(void)
1786 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1789 void InitUserDataDirectory(void)
1791 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1795 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1796 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1800 void SetFilePermissions(char *filename, int permission_class)
1802 int running_setgid = posix_process_running_setgid();
1803 int perms = (permission_class == PERMS_PRIVATE ?
1804 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1806 if (permission_class == PERMS_PUBLIC && !running_setgid)
1807 perms = FILE_PERMS_PUBLIC_ALL;
1809 chmod(filename, perms);
1812 char *getCookie(char *file_type)
1814 static char cookie[MAX_COOKIE_LEN + 1];
1816 if (strlen(program.cookie_prefix) + 1 +
1817 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1818 return "[COOKIE ERROR]"; // should never happen
1820 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1821 program.cookie_prefix, file_type,
1822 program.version_super, program.version_major);
1827 void fprintFileHeader(FILE *file, char *basename)
1829 char *prefix = "# ";
1832 fprintf_line_with_prefix(file, prefix, sep1, 77);
1833 fprintf(file, "%s%s\n", prefix, basename);
1834 fprintf_line_with_prefix(file, prefix, sep1, 77);
1835 fprintf(file, "\n");
1838 int getFileVersionFromCookieString(const char *cookie)
1840 const char *ptr_cookie1, *ptr_cookie2;
1841 const char *pattern1 = "_FILE_VERSION_";
1842 const char *pattern2 = "?.?";
1843 const int len_cookie = strlen(cookie);
1844 const int len_pattern1 = strlen(pattern1);
1845 const int len_pattern2 = strlen(pattern2);
1846 const int len_pattern = len_pattern1 + len_pattern2;
1847 int version_super, version_major;
1849 if (len_cookie <= len_pattern)
1852 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1853 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1855 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1858 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1859 ptr_cookie2[1] != '.' ||
1860 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1863 version_super = ptr_cookie2[0] - '0';
1864 version_major = ptr_cookie2[2] - '0';
1866 return VERSION_IDENT(version_super, version_major, 0, 0);
1869 boolean checkCookieString(const char *cookie, const char *template)
1871 const char *pattern = "_FILE_VERSION_?.?";
1872 const int len_cookie = strlen(cookie);
1873 const int len_template = strlen(template);
1874 const int len_pattern = strlen(pattern);
1876 if (len_cookie != len_template)
1879 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1886 // ----------------------------------------------------------------------------
1887 // setup file list and hash handling functions
1888 // ----------------------------------------------------------------------------
1890 char *getFormattedSetupEntry(char *token, char *value)
1893 static char entry[MAX_LINE_LEN];
1895 // if value is an empty string, just return token without value
1899 // start with the token and some spaces to format output line
1900 sprintf(entry, "%s:", token);
1901 for (i = strlen(entry); i < token_value_position; i++)
1904 // continue with the token's value
1905 strcat(entry, value);
1910 SetupFileList *newSetupFileList(char *token, char *value)
1912 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1914 new->token = getStringCopy(token);
1915 new->value = getStringCopy(value);
1922 void freeSetupFileList(SetupFileList *list)
1927 checked_free(list->token);
1928 checked_free(list->value);
1931 freeSetupFileList(list->next);
1936 char *getListEntry(SetupFileList *list, char *token)
1941 if (strEqual(list->token, token))
1944 return getListEntry(list->next, token);
1947 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1952 if (strEqual(list->token, token))
1954 checked_free(list->value);
1956 list->value = getStringCopy(value);
1960 else if (list->next == NULL)
1961 return (list->next = newSetupFileList(token, value));
1963 return setListEntry(list->next, token, value);
1966 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1971 if (list->next == NULL)
1972 return (list->next = newSetupFileList(token, value));
1974 return addListEntry(list->next, token, value);
1977 #if ENABLE_UNUSED_CODE
1979 static void printSetupFileList(SetupFileList *list)
1984 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1985 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1987 printSetupFileList(list->next);
1993 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1994 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1995 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1996 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1998 #define insert_hash_entry hashtable_insert
1999 #define search_hash_entry hashtable_search
2000 #define change_hash_entry hashtable_change
2001 #define remove_hash_entry hashtable_remove
2004 unsigned int get_hash_from_key(void *key)
2009 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2010 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2011 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2012 it works better than many other constants, prime or not) has never been
2013 adequately explained.
2015 If you just want to have a good hash function, and cannot wait, djb2
2016 is one of the best string hash functions i know. It has excellent
2017 distribution and speed on many different sets of keys and table sizes.
2018 You are not likely to do better with one of the "well known" functions
2019 such as PJW, K&R, etc.
2021 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2024 char *str = (char *)key;
2025 unsigned int hash = 5381;
2028 while ((c = *str++))
2029 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2034 static int keys_are_equal(void *key1, void *key2)
2036 return (strEqual((char *)key1, (char *)key2));
2039 SetupFileHash *newSetupFileHash(void)
2041 SetupFileHash *new_hash =
2042 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2044 if (new_hash == NULL)
2045 Fail("create_hashtable() failed -- out of memory");
2050 void freeSetupFileHash(SetupFileHash *hash)
2055 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2058 char *getHashEntry(SetupFileHash *hash, char *token)
2063 return search_hash_entry(hash, token);
2066 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2073 value_copy = getStringCopy(value);
2075 // change value; if it does not exist, insert it as new
2076 if (!change_hash_entry(hash, token, value_copy))
2077 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2078 Fail("cannot insert into hash -- aborting");
2081 char *removeHashEntry(SetupFileHash *hash, char *token)
2086 return remove_hash_entry(hash, token);
2089 #if ENABLE_UNUSED_CODE
2091 static void printSetupFileHash(SetupFileHash *hash)
2093 BEGIN_HASH_ITERATION(hash, itr)
2095 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2096 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2098 END_HASH_ITERATION(hash, itr)
2103 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2104 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2105 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2107 static boolean token_value_separator_found = FALSE;
2108 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2109 static boolean token_value_separator_warning = FALSE;
2111 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2112 static boolean token_already_exists_warning = FALSE;
2115 static boolean getTokenValueFromSetupLineExt(char *line,
2116 char **token_ptr, char **value_ptr,
2117 char *filename, char *line_raw,
2119 boolean separator_required)
2121 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2122 char *token, *value, *line_ptr;
2124 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2125 if (line_raw == NULL)
2127 strncpy(line_copy, line, MAX_LINE_LEN);
2128 line_copy[MAX_LINE_LEN] = '\0';
2131 strcpy(line_raw_copy, line_copy);
2132 line_raw = line_raw_copy;
2135 // cut trailing comment from input line
2136 for (line_ptr = line; *line_ptr; line_ptr++)
2138 if (*line_ptr == '#')
2145 // cut trailing whitespaces from input line
2146 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2147 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2150 // ignore empty lines
2154 // cut leading whitespaces from token
2155 for (token = line; *token; token++)
2156 if (*token != ' ' && *token != '\t')
2159 // start with empty value as reliable default
2162 token_value_separator_found = FALSE;
2164 // find end of token to determine start of value
2165 for (line_ptr = token; *line_ptr; line_ptr++)
2167 // first look for an explicit token/value separator, like ':' or '='
2168 if (*line_ptr == ':' || *line_ptr == '=')
2170 *line_ptr = '\0'; // terminate token string
2171 value = line_ptr + 1; // set beginning of value
2173 token_value_separator_found = TRUE;
2179 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2180 // fallback: if no token/value separator found, also allow whitespaces
2181 if (!token_value_separator_found && !separator_required)
2183 for (line_ptr = token; *line_ptr; line_ptr++)
2185 if (*line_ptr == ' ' || *line_ptr == '\t')
2187 *line_ptr = '\0'; // terminate token string
2188 value = line_ptr + 1; // set beginning of value
2190 token_value_separator_found = TRUE;
2196 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2197 if (token_value_separator_found)
2199 if (!token_value_separator_warning)
2201 Debug("setup", "---");
2203 if (filename != NULL)
2205 Debug("setup", "missing token/value separator(s) in config file:");
2206 Debug("setup", "- config file: '%s'", filename);
2210 Debug("setup", "missing token/value separator(s):");
2213 token_value_separator_warning = TRUE;
2216 if (filename != NULL)
2217 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2219 Debug("setup", "- line: '%s'", line_raw);
2225 // cut trailing whitespaces from token
2226 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2227 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2230 // cut leading whitespaces from value
2231 for (; *value; value++)
2232 if (*value != ' ' && *value != '\t')
2241 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2243 // while the internal (old) interface does not require a token/value
2244 // separator (for downwards compatibility with existing files which
2245 // don't use them), it is mandatory for the external (new) interface
2247 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2250 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2251 boolean top_recursion_level, boolean is_hash)
2253 static SetupFileHash *include_filename_hash = NULL;
2254 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2255 char *token, *value, *line_ptr;
2256 void *insert_ptr = NULL;
2257 boolean read_continued_line = FALSE;
2259 int line_nr = 0, token_count = 0, include_count = 0;
2261 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2262 token_value_separator_warning = FALSE;
2265 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2266 token_already_exists_warning = FALSE;
2269 if (!(file = openFile(filename, MODE_READ)))
2271 #if DEBUG_NO_CONFIG_FILE
2272 Debug("setup", "cannot open configuration file '%s'", filename);
2278 // use "insert pointer" to store list end for constant insertion complexity
2280 insert_ptr = setup_file_data;
2282 // on top invocation, create hash to mark included files (to prevent loops)
2283 if (top_recursion_level)
2284 include_filename_hash = newSetupFileHash();
2286 // mark this file as already included (to prevent including it again)
2287 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2289 while (!checkEndOfFile(file))
2291 // read next line of input file
2292 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2295 // check if line was completely read and is terminated by line break
2296 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2299 // cut trailing line break (this can be newline and/or carriage return)
2300 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2301 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2304 // copy raw input line for later use (mainly debugging output)
2305 strcpy(line_raw, line);
2307 if (read_continued_line)
2309 // append new line to existing line, if there is enough space
2310 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2311 strcat(previous_line, line_ptr);
2313 strcpy(line, previous_line); // copy storage buffer to line
2315 read_continued_line = FALSE;
2318 // if the last character is '\', continue at next line
2319 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2321 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2322 strcpy(previous_line, line); // copy line to storage buffer
2324 read_continued_line = TRUE;
2329 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2330 line_raw, line_nr, FALSE))
2335 if (strEqual(token, "include"))
2337 if (getHashEntry(include_filename_hash, value) == NULL)
2339 char *basepath = getBasePath(filename);
2340 char *basename = getBaseName(value);
2341 char *filename_include = getPath2(basepath, basename);
2343 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2347 free(filename_include);
2353 Warn("ignoring already processed file '%s'", value);
2360 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2362 getHashEntry((SetupFileHash *)setup_file_data, token);
2364 if (old_value != NULL)
2366 if (!token_already_exists_warning)
2368 Debug("setup", "---");
2369 Debug("setup", "duplicate token(s) found in config file:");
2370 Debug("setup", "- config file: '%s'", filename);
2372 token_already_exists_warning = TRUE;
2375 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2376 Debug("setup", " old value: '%s'", old_value);
2377 Debug("setup", " new value: '%s'", value);
2381 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2385 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2395 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2396 if (token_value_separator_warning)
2397 Debug("setup", "---");
2400 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2401 if (token_already_exists_warning)
2402 Debug("setup", "---");
2405 if (token_count == 0 && include_count == 0)
2406 Warn("configuration file '%s' is empty", filename);
2408 if (top_recursion_level)
2409 freeSetupFileHash(include_filename_hash);
2414 static int compareSetupFileData(const void *object1, const void *object2)
2416 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2417 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2419 return strcmp(entry1->token, entry2->token);
2422 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2424 int item_count = hashtable_count(hash);
2425 int item_size = sizeof(struct ConfigInfo);
2426 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2430 // copy string pointers from hash to array
2431 BEGIN_HASH_ITERATION(hash, itr)
2433 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2434 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2438 if (i > item_count) // should never happen
2441 END_HASH_ITERATION(hash, itr)
2443 // sort string pointers from hash in array
2444 qsort(sort_array, item_count, item_size, compareSetupFileData);
2446 if (!(file = fopen(filename, MODE_WRITE)))
2448 Warn("cannot write configuration file '%s'", filename);
2453 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2454 program.version_string));
2455 for (i = 0; i < item_count; i++)
2456 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2457 sort_array[i].value));
2460 checked_free(sort_array);
2463 SetupFileList *loadSetupFileList(char *filename)
2465 SetupFileList *setup_file_list = newSetupFileList("", "");
2466 SetupFileList *first_valid_list_entry;
2468 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2470 freeSetupFileList(setup_file_list);
2475 first_valid_list_entry = setup_file_list->next;
2477 // free empty list header
2478 setup_file_list->next = NULL;
2479 freeSetupFileList(setup_file_list);
2481 return first_valid_list_entry;
2484 SetupFileHash *loadSetupFileHash(char *filename)
2486 SetupFileHash *setup_file_hash = newSetupFileHash();
2488 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2490 freeSetupFileHash(setup_file_hash);
2495 return setup_file_hash;
2499 // ============================================================================
2501 // ============================================================================
2503 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2504 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2505 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2506 #define TOKEN_STR_LAST_USER "last_user"
2508 // level directory info
2509 #define LEVELINFO_TOKEN_IDENTIFIER 0
2510 #define LEVELINFO_TOKEN_NAME 1
2511 #define LEVELINFO_TOKEN_NAME_SORTING 2
2512 #define LEVELINFO_TOKEN_AUTHOR 3
2513 #define LEVELINFO_TOKEN_YEAR 4
2514 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2515 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2516 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2517 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2518 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2519 #define LEVELINFO_TOKEN_TESTED_BY 10
2520 #define LEVELINFO_TOKEN_LEVELS 11
2521 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2522 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2523 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2524 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2525 #define LEVELINFO_TOKEN_READONLY 16
2526 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2527 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2528 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2529 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2530 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2531 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2532 #define LEVELINFO_TOKEN_MUSIC_SET 23
2533 #define LEVELINFO_TOKEN_FILENAME 24
2534 #define LEVELINFO_TOKEN_FILETYPE 25
2535 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2536 #define LEVELINFO_TOKEN_HANDICAP 27
2537 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2538 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2540 #define NUM_LEVELINFO_TOKENS 30
2542 static LevelDirTree ldi;
2544 static struct TokenInfo levelinfo_tokens[] =
2546 // level directory info
2547 { TYPE_STRING, &ldi.identifier, "identifier" },
2548 { TYPE_STRING, &ldi.name, "name" },
2549 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2550 { TYPE_STRING, &ldi.author, "author" },
2551 { TYPE_STRING, &ldi.year, "year" },
2552 { TYPE_STRING, &ldi.program_title, "program_title" },
2553 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2554 { TYPE_STRING, &ldi.program_company, "program_company" },
2555 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2556 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2557 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2558 { TYPE_INTEGER, &ldi.levels, "levels" },
2559 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2560 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2561 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2562 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2563 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2564 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2565 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2566 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2567 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2568 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2569 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2570 { TYPE_STRING, &ldi.music_set, "music_set" },
2571 { TYPE_STRING, &ldi.level_filename, "filename" },
2572 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2573 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2574 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2575 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2576 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2579 static struct TokenInfo artworkinfo_tokens[] =
2581 // artwork directory info
2582 { TYPE_STRING, &ldi.identifier, "identifier" },
2583 { TYPE_STRING, &ldi.subdir, "subdir" },
2584 { TYPE_STRING, &ldi.name, "name" },
2585 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2586 { TYPE_STRING, &ldi.author, "author" },
2587 { TYPE_STRING, &ldi.program_title, "program_title" },
2588 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2589 { TYPE_STRING, &ldi.program_company, "program_company" },
2590 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2591 { TYPE_STRING, &ldi.basepath, "basepath" },
2592 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2593 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2594 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2599 static char *optional_tokens[] =
2602 "program_copyright",
2608 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2612 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2613 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2614 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2615 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2618 ti->node_parent = NULL;
2619 ti->node_group = NULL;
2626 ti->fullpath = NULL;
2627 ti->basepath = NULL;
2628 ti->identifier = NULL;
2629 ti->name = getStringCopy(ANONYMOUS_NAME);
2630 ti->name_sorting = NULL;
2631 ti->author = getStringCopy(ANONYMOUS_NAME);
2634 ti->program_title = NULL;
2635 ti->program_copyright = NULL;
2636 ti->program_company = NULL;
2638 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2639 ti->latest_engine = FALSE; // default: get from level
2640 ti->parent_link = FALSE;
2641 ti->is_copy = FALSE;
2642 ti->in_user_dir = FALSE;
2643 ti->user_defined = FALSE;
2645 ti->class_desc = NULL;
2647 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2649 if (ti->type == TREE_TYPE_LEVEL_DIR)
2651 ti->imported_from = NULL;
2652 ti->imported_by = NULL;
2653 ti->tested_by = NULL;
2655 ti->graphics_set_ecs = NULL;
2656 ti->graphics_set_aga = NULL;
2657 ti->graphics_set = NULL;
2658 ti->sounds_set_default = NULL;
2659 ti->sounds_set_lowpass = NULL;
2660 ti->sounds_set = NULL;
2661 ti->music_set = NULL;
2662 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2663 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2664 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2666 ti->level_filename = NULL;
2667 ti->level_filetype = NULL;
2669 ti->special_flags = NULL;
2672 ti->first_level = 0;
2674 ti->level_group = FALSE;
2675 ti->handicap_level = 0;
2676 ti->readonly = TRUE;
2677 ti->handicap = TRUE;
2678 ti->skip_levels = FALSE;
2680 ti->use_emc_tiles = FALSE;
2684 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2688 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2690 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2695 // copy all values from the parent structure
2697 ti->type = parent->type;
2699 ti->node_top = parent->node_top;
2700 ti->node_parent = parent;
2701 ti->node_group = NULL;
2708 ti->fullpath = NULL;
2709 ti->basepath = NULL;
2710 ti->identifier = NULL;
2711 ti->name = getStringCopy(ANONYMOUS_NAME);
2712 ti->name_sorting = NULL;
2713 ti->author = getStringCopy(parent->author);
2714 ti->year = getStringCopy(parent->year);
2716 ti->program_title = getStringCopy(parent->program_title);
2717 ti->program_copyright = getStringCopy(parent->program_copyright);
2718 ti->program_company = getStringCopy(parent->program_company);
2720 ti->sort_priority = parent->sort_priority;
2721 ti->latest_engine = parent->latest_engine;
2722 ti->parent_link = FALSE;
2723 ti->is_copy = FALSE;
2724 ti->in_user_dir = parent->in_user_dir;
2725 ti->user_defined = parent->user_defined;
2726 ti->color = parent->color;
2727 ti->class_desc = getStringCopy(parent->class_desc);
2729 ti->infotext = getStringCopy(parent->infotext);
2731 if (ti->type == TREE_TYPE_LEVEL_DIR)
2733 ti->imported_from = getStringCopy(parent->imported_from);
2734 ti->imported_by = getStringCopy(parent->imported_by);
2735 ti->tested_by = getStringCopy(parent->tested_by);
2737 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2738 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2739 ti->graphics_set = getStringCopy(parent->graphics_set);
2740 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2741 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2742 ti->sounds_set = getStringCopy(parent->sounds_set);
2743 ti->music_set = getStringCopy(parent->music_set);
2744 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2745 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2746 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2748 ti->level_filename = getStringCopy(parent->level_filename);
2749 ti->level_filetype = getStringCopy(parent->level_filetype);
2751 ti->special_flags = getStringCopy(parent->special_flags);
2753 ti->levels = parent->levels;
2754 ti->first_level = parent->first_level;
2755 ti->last_level = parent->last_level;
2756 ti->level_group = FALSE;
2757 ti->handicap_level = parent->handicap_level;
2758 ti->readonly = parent->readonly;
2759 ti->handicap = parent->handicap;
2760 ti->skip_levels = parent->skip_levels;
2762 ti->use_emc_tiles = parent->use_emc_tiles;
2766 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2768 TreeInfo *ti_copy = newTreeInfo();
2770 // copy all values from the original structure
2772 ti_copy->type = ti->type;
2774 ti_copy->node_top = ti->node_top;
2775 ti_copy->node_parent = ti->node_parent;
2776 ti_copy->node_group = ti->node_group;
2777 ti_copy->next = ti->next;
2779 ti_copy->cl_first = ti->cl_first;
2780 ti_copy->cl_cursor = ti->cl_cursor;
2782 ti_copy->subdir = getStringCopy(ti->subdir);
2783 ti_copy->fullpath = getStringCopy(ti->fullpath);
2784 ti_copy->basepath = getStringCopy(ti->basepath);
2785 ti_copy->identifier = getStringCopy(ti->identifier);
2786 ti_copy->name = getStringCopy(ti->name);
2787 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2788 ti_copy->author = getStringCopy(ti->author);
2789 ti_copy->year = getStringCopy(ti->year);
2791 ti_copy->program_title = getStringCopy(ti->program_title);
2792 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2793 ti_copy->program_company = getStringCopy(ti->program_company);
2795 ti_copy->imported_from = getStringCopy(ti->imported_from);
2796 ti_copy->imported_by = getStringCopy(ti->imported_by);
2797 ti_copy->tested_by = getStringCopy(ti->tested_by);
2799 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2800 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2801 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2802 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2803 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2804 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2805 ti_copy->music_set = getStringCopy(ti->music_set);
2806 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2807 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2808 ti_copy->music_path = getStringCopy(ti->music_path);
2810 ti_copy->level_filename = getStringCopy(ti->level_filename);
2811 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2813 ti_copy->special_flags = getStringCopy(ti->special_flags);
2815 ti_copy->levels = ti->levels;
2816 ti_copy->first_level = ti->first_level;
2817 ti_copy->last_level = ti->last_level;
2818 ti_copy->sort_priority = ti->sort_priority;
2820 ti_copy->latest_engine = ti->latest_engine;
2822 ti_copy->level_group = ti->level_group;
2823 ti_copy->parent_link = ti->parent_link;
2824 ti_copy->is_copy = ti->is_copy;
2825 ti_copy->in_user_dir = ti->in_user_dir;
2826 ti_copy->user_defined = ti->user_defined;
2827 ti_copy->readonly = ti->readonly;
2828 ti_copy->handicap = ti->handicap;
2829 ti_copy->skip_levels = ti->skip_levels;
2831 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2833 ti_copy->color = ti->color;
2834 ti_copy->class_desc = getStringCopy(ti->class_desc);
2835 ti_copy->handicap_level = ti->handicap_level;
2837 ti_copy->infotext = getStringCopy(ti->infotext);
2842 void freeTreeInfo(TreeInfo *ti)
2847 checked_free(ti->subdir);
2848 checked_free(ti->fullpath);
2849 checked_free(ti->basepath);
2850 checked_free(ti->identifier);
2852 checked_free(ti->name);
2853 checked_free(ti->name_sorting);
2854 checked_free(ti->author);
2855 checked_free(ti->year);
2857 checked_free(ti->program_title);
2858 checked_free(ti->program_copyright);
2859 checked_free(ti->program_company);
2861 checked_free(ti->class_desc);
2863 checked_free(ti->infotext);
2865 if (ti->type == TREE_TYPE_LEVEL_DIR)
2867 checked_free(ti->imported_from);
2868 checked_free(ti->imported_by);
2869 checked_free(ti->tested_by);
2871 checked_free(ti->graphics_set_ecs);
2872 checked_free(ti->graphics_set_aga);
2873 checked_free(ti->graphics_set);
2874 checked_free(ti->sounds_set_default);
2875 checked_free(ti->sounds_set_lowpass);
2876 checked_free(ti->sounds_set);
2877 checked_free(ti->music_set);
2879 checked_free(ti->graphics_path);
2880 checked_free(ti->sounds_path);
2881 checked_free(ti->music_path);
2883 checked_free(ti->level_filename);
2884 checked_free(ti->level_filetype);
2886 checked_free(ti->special_flags);
2889 // recursively free child node
2891 freeTreeInfo(ti->node_group);
2893 // recursively free next node
2895 freeTreeInfo(ti->next);
2900 void setSetupInfo(struct TokenInfo *token_info,
2901 int token_nr, char *token_value)
2903 int token_type = token_info[token_nr].type;
2904 void *setup_value = token_info[token_nr].value;
2906 if (token_value == NULL)
2909 // set setup field to corresponding token value
2914 *(boolean *)setup_value = get_boolean_from_string(token_value);
2918 *(int *)setup_value = get_switch3_from_string(token_value);
2922 *(Key *)setup_value = getKeyFromKeyName(token_value);
2926 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2930 *(int *)setup_value = get_integer_from_string(token_value);
2934 checked_free(*(char **)setup_value);
2935 *(char **)setup_value = getStringCopy(token_value);
2939 *(int *)setup_value = get_player_nr_from_string(token_value);
2947 static int compareTreeInfoEntries(const void *object1, const void *object2)
2949 const TreeInfo *entry1 = *((TreeInfo **)object1);
2950 const TreeInfo *entry2 = *((TreeInfo **)object2);
2951 int tree_sorting1 = TREE_SORTING(entry1);
2952 int tree_sorting2 = TREE_SORTING(entry2);
2954 if (tree_sorting1 != tree_sorting2)
2955 return (tree_sorting1 - tree_sorting2);
2957 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2960 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2964 if (node_parent == NULL)
2967 ti_new = newTreeInfo();
2968 setTreeInfoToDefaults(ti_new, node_parent->type);
2970 ti_new->node_parent = node_parent;
2971 ti_new->parent_link = TRUE;
2973 setString(&ti_new->identifier, node_parent->identifier);
2974 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2975 setString(&ti_new->name_sorting, ti_new->name);
2977 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2978 setString(&ti_new->fullpath, node_parent->fullpath);
2980 ti_new->sort_priority = LEVELCLASS_PARENT;
2981 ti_new->latest_engine = node_parent->latest_engine;
2983 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2985 pushTreeInfo(&node_parent->node_group, ti_new);
2990 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2992 if (node_first == NULL)
2995 TreeInfo *ti_new = newTreeInfo();
2996 int type = node_first->type;
2998 setTreeInfoToDefaults(ti_new, type);
3000 ti_new->node_parent = NULL;
3001 ti_new->parent_link = FALSE;
3003 setString(&ti_new->identifier, "top_tree_node");
3004 setString(&ti_new->name, TREE_INFOTEXT(type));
3005 setString(&ti_new->name_sorting, ti_new->name);
3007 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3008 setString(&ti_new->fullpath, ".");
3010 ti_new->sort_priority = LEVELCLASS_TOP;
3011 ti_new->latest_engine = node_first->latest_engine;
3013 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3015 ti_new->node_group = node_first;
3016 ti_new->level_group = TRUE;
3018 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3020 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3021 setString(&ti_new2->name_sorting, ti_new2->name);
3026 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3030 if (node->node_group)
3031 setTreeInfoParentNodes(node->node_group, node);
3033 node->node_parent = node_parent;
3040 // ----------------------------------------------------------------------------
3041 // functions for handling level and custom artwork info cache
3042 // ----------------------------------------------------------------------------
3044 static void LoadArtworkInfoCache(void)
3046 InitCacheDirectory();
3048 if (artworkinfo_cache_old == NULL)
3050 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3052 // try to load artwork info hash from already existing cache file
3053 artworkinfo_cache_old = loadSetupFileHash(filename);
3055 // try to get program version that artwork info cache was written with
3056 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3058 // check program version of artwork info cache against current version
3059 if (!strEqual(version, program.version_string))
3061 freeSetupFileHash(artworkinfo_cache_old);
3063 artworkinfo_cache_old = NULL;
3066 // if no artwork info cache file was found, start with empty hash
3067 if (artworkinfo_cache_old == NULL)
3068 artworkinfo_cache_old = newSetupFileHash();
3073 if (artworkinfo_cache_new == NULL)
3074 artworkinfo_cache_new = newSetupFileHash();
3076 update_artworkinfo_cache = FALSE;
3079 static void SaveArtworkInfoCache(void)
3081 if (!update_artworkinfo_cache)
3084 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3086 InitCacheDirectory();
3088 saveSetupFileHash(artworkinfo_cache_new, filename);
3093 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3095 static char *prefix = NULL;
3097 checked_free(prefix);
3099 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3104 // (identical to above function, but separate string buffer needed -- nasty)
3105 static char *getCacheToken(char *prefix, char *suffix)
3107 static char *token = NULL;
3109 checked_free(token);
3111 token = getStringCat2WithSeparator(prefix, suffix, ".");
3116 static char *getFileTimestampString(char *filename)
3118 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3121 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3123 struct stat file_status;
3125 if (timestamp_string == NULL)
3128 if (!fileExists(filename)) // file does not exist
3129 return (atoi(timestamp_string) != 0);
3131 if (stat(filename, &file_status) != 0) // cannot stat file
3134 return (file_status.st_mtime != atoi(timestamp_string));
3137 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3139 char *identifier = level_node->subdir;
3140 char *type_string = ARTWORK_DIRECTORY(type);
3141 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3142 char *token_main = getCacheToken(token_prefix, "CACHED");
3143 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3144 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3145 TreeInfo *artwork_info = NULL;
3147 if (!use_artworkinfo_cache)
3150 if (optional_tokens_hash == NULL)
3154 // create hash from list of optional tokens (for quick access)
3155 optional_tokens_hash = newSetupFileHash();
3156 for (i = 0; optional_tokens[i] != NULL; i++)
3157 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3164 artwork_info = newTreeInfo();
3165 setTreeInfoToDefaults(artwork_info, type);
3167 // set all structure fields according to the token/value pairs
3168 ldi = *artwork_info;
3169 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3171 char *token_suffix = artworkinfo_tokens[i].text;
3172 char *token = getCacheToken(token_prefix, token_suffix);
3173 char *value = getHashEntry(artworkinfo_cache_old, token);
3175 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3177 setSetupInfo(artworkinfo_tokens, i, value);
3179 // check if cache entry for this item is mandatory, but missing
3180 if (value == NULL && !optional)
3182 Warn("missing cache entry '%s'", token);
3188 *artwork_info = ldi;
3193 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3194 LEVELINFO_FILENAME);
3195 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3196 ARTWORKINFO_FILENAME(type));
3198 // check if corresponding "levelinfo.conf" file has changed
3199 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3200 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3202 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3205 // check if corresponding "<artworkinfo>.conf" file has changed
3206 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3207 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3209 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3212 checked_free(filename_levelinfo);
3213 checked_free(filename_artworkinfo);
3216 if (!cached && artwork_info != NULL)
3218 freeTreeInfo(artwork_info);
3223 return artwork_info;
3226 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3227 LevelDirTree *level_node, int type)
3229 char *identifier = level_node->subdir;
3230 char *type_string = ARTWORK_DIRECTORY(type);
3231 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3232 char *token_main = getCacheToken(token_prefix, "CACHED");
3233 boolean set_cache_timestamps = TRUE;
3236 setHashEntry(artworkinfo_cache_new, token_main, "true");
3238 if (set_cache_timestamps)
3240 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3241 LEVELINFO_FILENAME);
3242 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3243 ARTWORKINFO_FILENAME(type));
3244 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3245 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3247 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3248 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3250 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3251 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3253 checked_free(filename_levelinfo);
3254 checked_free(filename_artworkinfo);
3255 checked_free(timestamp_levelinfo);
3256 checked_free(timestamp_artworkinfo);
3259 ldi = *artwork_info;
3260 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3262 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3263 char *value = getSetupValue(artworkinfo_tokens[i].type,
3264 artworkinfo_tokens[i].value);
3266 setHashEntry(artworkinfo_cache_new, token, value);
3271 // ----------------------------------------------------------------------------
3272 // functions for loading level info and custom artwork info
3273 // ----------------------------------------------------------------------------
3275 int GetZipFileTreeType(char *zip_filename)
3277 static char *top_dir_path = NULL;
3278 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3279 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3281 GRAPHICSINFO_FILENAME,
3282 SOUNDSINFO_FILENAME,
3288 checked_free(top_dir_path);
3289 top_dir_path = NULL;
3291 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3293 checked_free(top_dir_conf_filename[j]);
3294 top_dir_conf_filename[j] = NULL;
3297 char **zip_entries = zip_list(zip_filename);
3299 // check if zip file successfully opened
3300 if (zip_entries == NULL || zip_entries[0] == NULL)
3301 return TREE_TYPE_UNDEFINED;
3303 // first zip file entry is expected to be top level directory
3304 char *top_dir = zip_entries[0];
3306 // check if valid top level directory found in zip file
3307 if (!strSuffix(top_dir, "/"))
3308 return TREE_TYPE_UNDEFINED;
3310 // get filenames of valid configuration files in top level directory
3311 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3312 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3314 int tree_type = TREE_TYPE_UNDEFINED;
3317 while (zip_entries[e] != NULL)
3319 // check if every zip file entry is below top level directory
3320 if (!strPrefix(zip_entries[e], top_dir))
3321 return TREE_TYPE_UNDEFINED;
3323 // check if this zip file entry is a valid configuration filename
3324 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3326 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3328 // only exactly one valid configuration file allowed
3329 if (tree_type != TREE_TYPE_UNDEFINED)
3330 return TREE_TYPE_UNDEFINED;
3342 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3345 static char *top_dir_path = NULL;
3346 static char *top_dir_conf_filename = NULL;
3348 checked_free(top_dir_path);
3349 checked_free(top_dir_conf_filename);
3351 top_dir_path = NULL;
3352 top_dir_conf_filename = NULL;
3354 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3355 ARTWORKINFO_FILENAME(tree_type));
3357 // check if valid configuration filename determined
3358 if (conf_basename == NULL || strEqual(conf_basename, ""))
3361 char **zip_entries = zip_list(zip_filename);
3363 // check if zip file successfully opened
3364 if (zip_entries == NULL || zip_entries[0] == NULL)
3367 // first zip file entry is expected to be top level directory
3368 char *top_dir = zip_entries[0];
3370 // check if valid top level directory found in zip file
3371 if (!strSuffix(top_dir, "/"))
3374 // get path of extracted top level directory
3375 top_dir_path = getPath2(directory, top_dir);
3377 // remove trailing directory separator from top level directory path
3378 // (required to be able to check for file and directory in next step)
3379 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3381 // check if zip file's top level directory already exists in target directory
3382 if (fileExists(top_dir_path)) // (checks for file and directory)
3385 // get filename of configuration file in top level directory
3386 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3388 boolean found_top_dir_conf_filename = FALSE;
3391 while (zip_entries[i] != NULL)
3393 // check if every zip file entry is below top level directory
3394 if (!strPrefix(zip_entries[i], top_dir))
3397 // check if this zip file entry is the configuration filename
3398 if (strEqual(zip_entries[i], top_dir_conf_filename))
3399 found_top_dir_conf_filename = TRUE;
3404 // check if valid configuration filename was found in zip file
3405 if (!found_top_dir_conf_filename)
3411 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3414 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3417 if (!zip_file_valid)
3419 Warn("zip file '%s' rejected!", zip_filename);
3424 char **zip_entries = zip_extract(zip_filename, directory);
3426 if (zip_entries == NULL)
3428 Warn("zip file '%s' could not be extracted!", zip_filename);
3433 Info("zip file '%s' successfully extracted!", zip_filename);
3435 // first zip file entry contains top level directory
3436 char *top_dir = zip_entries[0];
3438 // remove trailing directory separator from top level directory
3439 top_dir[strlen(top_dir) - 1] = '\0';
3444 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3447 DirectoryEntry *dir_entry;
3449 if ((dir = openDirectory(directory)) == NULL)
3451 // display error if directory is main "options.graphics_directory" etc.
3452 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3453 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3454 Warn("cannot read directory '%s'", directory);
3459 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3461 // skip non-zip files (and also directories with zip extension)
3462 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3465 char *zip_filename = getPath2(directory, dir_entry->basename);
3466 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3467 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3469 // check if zip file hasn't already been extracted or rejected
3470 if (!fileExists(zip_filename_extracted) &&
3471 !fileExists(zip_filename_rejected))
3473 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3475 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3476 zip_filename_rejected);
3479 // create empty file to mark zip file as extracted or rejected
3480 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3481 fclose(marker_file);
3484 free(zip_filename_extracted);
3485 free(zip_filename_rejected);
3489 closeDirectory(dir);
3492 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3493 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3495 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3496 TreeInfo *node_parent,
3497 char *level_directory,
3498 char *directory_name)
3500 char *directory_path = getPath2(level_directory, directory_name);
3501 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3502 SetupFileHash *setup_file_hash;
3503 LevelDirTree *leveldir_new = NULL;
3506 // unless debugging, silently ignore directories without "levelinfo.conf"
3507 if (!options.debug && !fileExists(filename))
3509 free(directory_path);
3515 setup_file_hash = loadSetupFileHash(filename);
3517 if (setup_file_hash == NULL)
3519 #if DEBUG_NO_CONFIG_FILE
3520 Debug("setup", "ignoring level directory '%s'", directory_path);
3523 free(directory_path);
3529 leveldir_new = newTreeInfo();
3532 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3534 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3536 leveldir_new->subdir = getStringCopy(directory_name);
3538 // set all structure fields according to the token/value pairs
3539 ldi = *leveldir_new;
3540 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3541 setSetupInfo(levelinfo_tokens, i,
3542 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3543 *leveldir_new = ldi;
3545 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3546 setString(&leveldir_new->name, leveldir_new->subdir);
3548 if (leveldir_new->identifier == NULL)
3549 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3551 if (leveldir_new->name_sorting == NULL)
3552 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3554 if (node_parent == NULL) // top level group
3556 leveldir_new->basepath = getStringCopy(level_directory);
3557 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3559 else // sub level group
3561 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3562 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3565 leveldir_new->last_level =
3566 leveldir_new->first_level + leveldir_new->levels - 1;
3568 leveldir_new->in_user_dir =
3569 (!strEqual(leveldir_new->basepath, options.level_directory));
3571 // adjust some settings if user's private level directory was detected
3572 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3573 leveldir_new->in_user_dir &&
3574 (strEqual(leveldir_new->subdir, getLoginName()) ||
3575 strEqual(leveldir_new->name, getLoginName()) ||
3576 strEqual(leveldir_new->author, getRealName())))
3578 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3579 leveldir_new->readonly = FALSE;
3582 leveldir_new->user_defined =
3583 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3585 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3587 leveldir_new->handicap_level = // set handicap to default value
3588 (leveldir_new->user_defined || !leveldir_new->handicap ?
3589 leveldir_new->last_level : leveldir_new->first_level);
3591 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3593 pushTreeInfo(node_first, leveldir_new);
3595 freeSetupFileHash(setup_file_hash);
3597 if (leveldir_new->level_group)
3599 // create node to link back to current level directory
3600 createParentTreeInfoNode(leveldir_new);
3602 // recursively step into sub-directory and look for more level series
3603 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3604 leveldir_new, directory_path);
3607 free(directory_path);
3613 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3614 TreeInfo *node_parent,
3615 char *level_directory)
3617 // ---------- 1st stage: process any level set zip files ----------
3619 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3621 // ---------- 2nd stage: check for level set directories ----------
3624 DirectoryEntry *dir_entry;
3625 boolean valid_entry_found = FALSE;
3627 if ((dir = openDirectory(level_directory)) == NULL)
3629 Warn("cannot read level directory '%s'", level_directory);
3634 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3636 char *directory_name = dir_entry->basename;
3637 char *directory_path = getPath2(level_directory, directory_name);
3639 // skip entries for current and parent directory
3640 if (strEqual(directory_name, ".") ||
3641 strEqual(directory_name, ".."))
3643 free(directory_path);
3648 // find out if directory entry is itself a directory
3649 if (!dir_entry->is_directory) // not a directory
3651 free(directory_path);
3656 free(directory_path);
3658 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3659 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3660 strEqual(directory_name, MUSIC_DIRECTORY))
3663 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3668 closeDirectory(dir);
3670 // special case: top level directory may directly contain "levelinfo.conf"
3671 if (node_parent == NULL && !valid_entry_found)
3673 // check if this directory directly contains a file "levelinfo.conf"
3674 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3675 level_directory, ".");
3678 if (!valid_entry_found)
3679 Warn("cannot find any valid level series in directory '%s'",
3683 boolean AdjustGraphicsForEMC(void)
3685 boolean settings_changed = FALSE;
3687 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3688 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3690 return settings_changed;
3693 boolean AdjustSoundsForEMC(void)
3695 boolean settings_changed = FALSE;
3697 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3698 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3700 return settings_changed;
3703 void LoadLevelInfo(void)
3705 InitUserLevelDirectory(getLoginName());
3707 DrawInitText("Loading level series", 120, FC_GREEN);
3709 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3710 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3712 leveldir_first = createTopTreeInfoNode(leveldir_first);
3714 /* after loading all level set information, clone the level directory tree
3715 and remove all level sets without levels (these may still contain artwork
3716 to be offered in the setup menu as "custom artwork", and are therefore
3717 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3718 leveldir_first_all = leveldir_first;
3719 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3721 AdjustGraphicsForEMC();
3722 AdjustSoundsForEMC();
3724 // before sorting, the first entries will be from the user directory
3725 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3727 if (leveldir_first == NULL)
3728 Fail("cannot find any valid level series in any directory");
3730 sortTreeInfo(&leveldir_first);
3732 #if ENABLE_UNUSED_CODE
3733 dumpTreeInfo(leveldir_first, 0);
3737 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3738 TreeInfo *node_parent,
3739 char *base_directory,
3740 char *directory_name, int type)
3742 char *directory_path = getPath2(base_directory, directory_name);
3743 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3744 SetupFileHash *setup_file_hash = NULL;
3745 TreeInfo *artwork_new = NULL;
3748 if (fileExists(filename))
3749 setup_file_hash = loadSetupFileHash(filename);
3751 if (setup_file_hash == NULL) // no config file -- look for artwork files
3754 DirectoryEntry *dir_entry;
3755 boolean valid_file_found = FALSE;
3757 if ((dir = openDirectory(directory_path)) != NULL)
3759 while ((dir_entry = readDirectory(dir)) != NULL)
3761 if (FileIsArtworkType(dir_entry->filename, type))
3763 valid_file_found = TRUE;
3769 closeDirectory(dir);
3772 if (!valid_file_found)
3774 #if DEBUG_NO_CONFIG_FILE
3775 if (!strEqual(directory_name, "."))
3776 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3779 free(directory_path);
3786 artwork_new = newTreeInfo();
3789 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3791 setTreeInfoToDefaults(artwork_new, type);
3793 artwork_new->subdir = getStringCopy(directory_name);
3795 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3797 // set all structure fields according to the token/value pairs
3799 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3800 setSetupInfo(levelinfo_tokens, i,
3801 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3804 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3805 setString(&artwork_new->name, artwork_new->subdir);
3807 if (artwork_new->identifier == NULL)
3808 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3810 if (artwork_new->name_sorting == NULL)
3811 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3814 if (node_parent == NULL) // top level group
3816 artwork_new->basepath = getStringCopy(base_directory);
3817 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3819 else // sub level group
3821 artwork_new->basepath = getStringCopy(node_parent->basepath);
3822 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3825 artwork_new->in_user_dir =
3826 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3828 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3830 if (setup_file_hash == NULL) // (after determining ".user_defined")
3832 if (strEqual(artwork_new->subdir, "."))
3834 if (artwork_new->user_defined)
3836 setString(&artwork_new->identifier, "private");
3837 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3841 setString(&artwork_new->identifier, "classic");
3842 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3845 setString(&artwork_new->class_desc,
3846 getLevelClassDescription(artwork_new));
3850 setString(&artwork_new->identifier, artwork_new->subdir);
3853 setString(&artwork_new->name, artwork_new->identifier);
3854 setString(&artwork_new->name_sorting, artwork_new->name);
3857 pushTreeInfo(node_first, artwork_new);
3859 freeSetupFileHash(setup_file_hash);
3861 free(directory_path);
3867 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3868 TreeInfo *node_parent,
3869 char *base_directory, int type)
3871 // ---------- 1st stage: process any artwork set zip files ----------
3873 ProcessZipFilesInDirectory(base_directory, type);
3875 // ---------- 2nd stage: check for artwork set directories ----------
3878 DirectoryEntry *dir_entry;
3879 boolean valid_entry_found = FALSE;
3881 if ((dir = openDirectory(base_directory)) == NULL)
3883 // display error if directory is main "options.graphics_directory" etc.
3884 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3885 Warn("cannot read directory '%s'", base_directory);
3890 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3892 char *directory_name = dir_entry->basename;
3893 char *directory_path = getPath2(base_directory, directory_name);
3895 // skip directory entries for current and parent directory
3896 if (strEqual(directory_name, ".") ||
3897 strEqual(directory_name, ".."))
3899 free(directory_path);
3904 // skip directory entries which are not a directory
3905 if (!dir_entry->is_directory) // not a directory
3907 free(directory_path);
3912 free(directory_path);
3914 // check if this directory contains artwork with or without config file
3915 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3917 directory_name, type);
3920 closeDirectory(dir);
3922 // check if this directory directly contains artwork itself
3923 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3924 base_directory, ".",
3926 if (!valid_entry_found)
3927 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3930 static TreeInfo *getDummyArtworkInfo(int type)
3932 // this is only needed when there is completely no artwork available
3933 TreeInfo *artwork_new = newTreeInfo();
3935 setTreeInfoToDefaults(artwork_new, type);
3937 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3938 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3939 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3941 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3942 setString(&artwork_new->name, UNDEFINED_FILENAME);
3943 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3948 void SetCurrentArtwork(int type)
3950 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3951 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3952 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3953 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3955 // set current artwork to artwork configured in setup menu
3956 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3958 // if not found, set current artwork to default artwork
3959 if (*current_ptr == NULL)
3960 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3962 // if not found, set current artwork to first artwork in tree
3963 if (*current_ptr == NULL)
3964 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3967 void ChangeCurrentArtworkIfNeeded(int type)
3969 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3970 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3972 if (!strEqual(current_identifier, setup_set))
3973 SetCurrentArtwork(type);
3976 void LoadArtworkInfo(void)
3978 LoadArtworkInfoCache();
3980 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3982 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3983 options.graphics_directory,
3984 TREE_TYPE_GRAPHICS_DIR);
3985 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3986 getUserGraphicsDir(),
3987 TREE_TYPE_GRAPHICS_DIR);
3989 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3990 options.sounds_directory,
3991 TREE_TYPE_SOUNDS_DIR);
3992 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3994 TREE_TYPE_SOUNDS_DIR);
3996 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3997 options.music_directory,
3998 TREE_TYPE_MUSIC_DIR);
3999 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4001 TREE_TYPE_MUSIC_DIR);
4003 if (artwork.gfx_first == NULL)
4004 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4005 if (artwork.snd_first == NULL)
4006 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4007 if (artwork.mus_first == NULL)
4008 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4010 // before sorting, the first entries will be from the user directory
4011 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4012 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4013 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4015 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4016 artwork.snd_current_identifier = artwork.snd_current->identifier;
4017 artwork.mus_current_identifier = artwork.mus_current->identifier;
4019 #if ENABLE_UNUSED_CODE
4020 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4021 artwork.gfx_current_identifier);
4022 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4023 artwork.snd_current_identifier);
4024 Debug("setup:LoadArtworkInfo", "music set == %s",
4025 artwork.mus_current_identifier);
4028 sortTreeInfo(&artwork.gfx_first);
4029 sortTreeInfo(&artwork.snd_first);
4030 sortTreeInfo(&artwork.mus_first);
4032 #if ENABLE_UNUSED_CODE
4033 dumpTreeInfo(artwork.gfx_first, 0);
4034 dumpTreeInfo(artwork.snd_first, 0);
4035 dumpTreeInfo(artwork.mus_first, 0);
4039 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4041 ArtworkDirTree *artwork_new = newTreeInfo();
4042 char *top_node_name = "standalone artwork";
4044 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4046 artwork_new->level_group = TRUE;
4048 setString(&artwork_new->identifier, top_node_name);
4049 setString(&artwork_new->name, top_node_name);
4050 setString(&artwork_new->name_sorting, top_node_name);
4052 // create node to link back to current custom artwork directory
4053 createParentTreeInfoNode(artwork_new);
4055 // move existing custom artwork tree into newly created sub-tree
4056 artwork_new->node_group->next = *artwork_node;
4058 // change custom artwork tree to contain only newly created node
4059 *artwork_node = artwork_new;
4062 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4063 ArtworkDirTree *node_parent,
4064 LevelDirTree *level_node,
4065 boolean empty_level_set_mode)
4067 int type = (*artwork_node)->type;
4069 // recursively check all level directories for artwork sub-directories
4073 boolean empty_level_set = (level_node->levels == 0);
4075 // check all tree entries for artwork, but skip parent link entries
4076 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4078 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4079 boolean cached = (artwork_new != NULL);
4083 pushTreeInfo(artwork_node, artwork_new);
4087 TreeInfo *topnode_last = *artwork_node;
4088 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4089 ARTWORK_DIRECTORY(type));
4091 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4093 if (topnode_last != *artwork_node) // check for newly added node
4095 artwork_new = *artwork_node;
4097 setString(&artwork_new->identifier, level_node->subdir);
4098 setString(&artwork_new->name, level_node->name);
4099 setString(&artwork_new->name_sorting, level_node->name_sorting);
4101 artwork_new->sort_priority = level_node->sort_priority;
4102 artwork_new->in_user_dir = level_node->in_user_dir;
4104 update_artworkinfo_cache = TRUE;
4110 // insert artwork info (from old cache or filesystem) into new cache
4111 if (artwork_new != NULL)
4112 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4115 DrawInitText(level_node->name, 150, FC_YELLOW);
4117 if (level_node->node_group != NULL)
4119 TreeInfo *artwork_new = newTreeInfo();
4122 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4124 setTreeInfoToDefaults(artwork_new, type);
4126 artwork_new->level_group = TRUE;
4128 setString(&artwork_new->identifier, level_node->subdir);
4130 if (node_parent == NULL) // check for top tree node
4132 char *top_node_name = (empty_level_set_mode ?
4133 "artwork for certain level sets" :
4134 "artwork included in level sets");
4136 setString(&artwork_new->name, top_node_name);
4137 setString(&artwork_new->name_sorting, top_node_name);
4141 setString(&artwork_new->name, level_node->name);
4142 setString(&artwork_new->name_sorting, level_node->name_sorting);
4145 pushTreeInfo(artwork_node, artwork_new);
4147 // create node to link back to current custom artwork directory
4148 createParentTreeInfoNode(artwork_new);
4150 // recursively step into sub-directory and look for more custom artwork
4151 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4152 level_node->node_group,
4153 empty_level_set_mode);
4155 // if sub-tree has no custom artwork at all, remove it
4156 if (artwork_new->node_group->next == NULL)
4157 removeTreeInfo(artwork_node);
4160 level_node = level_node->next;
4164 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4166 // move peviously loaded artwork tree into separate sub-tree
4167 MoveArtworkInfoIntoSubTree(artwork_node);
4169 // load artwork from level sets into separate sub-trees
4170 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4171 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4173 // add top tree node over all three separate sub-trees
4174 *artwork_node = createTopTreeInfoNode(*artwork_node);
4176 // set all parent links (back links) in complete artwork tree
4177 setTreeInfoParentNodes(*artwork_node, NULL);
4180 void LoadLevelArtworkInfo(void)
4182 print_timestamp_init("LoadLevelArtworkInfo");
4184 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4186 print_timestamp_time("DrawTimeText");
4188 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4189 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4190 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4191 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4192 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4193 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4195 SaveArtworkInfoCache();
4197 print_timestamp_time("SaveArtworkInfoCache");
4199 // needed for reloading level artwork not known at ealier stage
4200 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4201 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4202 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4204 print_timestamp_time("getTreeInfoFromIdentifier");
4206 sortTreeInfo(&artwork.gfx_first);
4207 sortTreeInfo(&artwork.snd_first);
4208 sortTreeInfo(&artwork.mus_first);
4210 print_timestamp_time("sortTreeInfo");
4212 #if ENABLE_UNUSED_CODE
4213 dumpTreeInfo(artwork.gfx_first, 0);
4214 dumpTreeInfo(artwork.snd_first, 0);
4215 dumpTreeInfo(artwork.mus_first, 0);
4218 print_timestamp_done("LoadLevelArtworkInfo");
4221 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4222 char *tree_subdir_new, int type)
4224 if (tree_node_old == NULL)
4226 if (type == TREE_TYPE_LEVEL_DIR)
4228 // get level info tree node of personal user level set
4229 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4231 // this may happen if "setup.internal.create_user_levelset" is FALSE
4232 // or if file "levelinfo.conf" is missing in personal user level set
4233 if (tree_node_old == NULL)
4234 tree_node_old = leveldir_first->node_group;
4238 // get artwork info tree node of first artwork set
4239 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4243 if (tree_dir == NULL)
4244 tree_dir = TREE_USERDIR(type);
4246 if (tree_node_old == NULL ||
4248 tree_subdir_new == NULL) // should not happen
4251 int draw_deactivation_mask = GetDrawDeactivationMask();
4253 // override draw deactivation mask (temporarily disable drawing)
4254 SetDrawDeactivationMask(REDRAW_ALL);
4256 if (type == TREE_TYPE_LEVEL_DIR)
4258 // load new level set config and add it next to first user level set
4259 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4260 tree_node_old->node_parent,
4261 tree_dir, tree_subdir_new);
4265 // load new artwork set config and add it next to first artwork set
4266 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4267 tree_node_old->node_parent,
4268 tree_dir, tree_subdir_new, type);
4271 // set draw deactivation mask to previous value
4272 SetDrawDeactivationMask(draw_deactivation_mask);
4274 // get first node of level or artwork info tree
4275 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4277 // get tree info node of newly added level or artwork set
4278 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4281 if (tree_node_new == NULL) // should not happen
4284 // correct top link and parent node link of newly created tree node
4285 tree_node_new->node_top = tree_node_old->node_top;
4286 tree_node_new->node_parent = tree_node_old->node_parent;
4288 // sort tree info to adjust position of newly added tree set
4289 sortTreeInfo(tree_node_first);
4294 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4295 char *tree_subdir_new, int type)
4297 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4298 Fail("internal tree info structure corrupted -- aborting");
4301 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4303 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4306 char *getArtworkIdentifierForUserLevelSet(int type)
4308 char *classic_artwork_set = getClassicArtworkSet(type);
4310 // check for custom artwork configured in "levelinfo.conf"
4311 char *leveldir_artwork_set =
4312 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4313 boolean has_leveldir_artwork_set =
4314 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4315 classic_artwork_set));
4317 // check for custom artwork in sub-directory "graphics" etc.
4318 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4319 char *leveldir_identifier = leveldir_current->identifier;
4320 boolean has_artwork_subdir =
4321 (getTreeInfoFromIdentifier(artwork_first_node,
4322 leveldir_identifier) != NULL);
4324 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4325 has_artwork_subdir ? leveldir_identifier :
4326 classic_artwork_set);
4329 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4331 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4332 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4333 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4337 ti = getTreeInfoFromIdentifier(artwork_first_node,
4338 ARTWORK_DEFAULT_SUBDIR(type));
4340 Fail("cannot find default graphics -- should not happen");
4346 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4348 char *graphics_set =
4349 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4351 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4353 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4355 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4356 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4357 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4360 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4361 char *level_author, int num_levels)
4363 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4364 char *filename_tmp = getStringCat2(filename, ".tmp");
4366 FILE *file_tmp = NULL;
4367 char line[MAX_LINE_LEN];
4368 boolean success = FALSE;
4369 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4371 // update values in level directory tree
4373 if (level_name != NULL)
4374 setString(&leveldir->name, level_name);
4376 if (level_author != NULL)
4377 setString(&leveldir->author, level_author);
4379 if (num_levels != -1)
4380 leveldir->levels = num_levels;
4382 // update values that depend on other values
4384 setString(&leveldir->name_sorting, leveldir->name);
4386 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4388 // sort order of level sets may have changed
4389 sortTreeInfo(&leveldir_first);
4391 if ((file = fopen(filename, MODE_READ)) &&
4392 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4394 while (fgets(line, MAX_LINE_LEN, file))
4396 if (strPrefix(line, "name:") && level_name != NULL)
4397 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4398 else if (strPrefix(line, "author:") && level_author != NULL)
4399 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4400 else if (strPrefix(line, "levels:") && num_levels != -1)
4401 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4403 fputs(line, file_tmp);
4416 success = (rename(filename_tmp, filename) == 0);
4424 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4425 char *level_author, int num_levels,
4426 boolean use_artwork_set)
4428 LevelDirTree *level_info;
4433 // create user level sub-directory, if needed
4434 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4436 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4438 if (!(file = fopen(filename, MODE_WRITE)))
4440 Warn("cannot write level info file '%s'", filename);
4447 level_info = newTreeInfo();
4449 // always start with reliable default values
4450 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4452 setString(&level_info->name, level_name);
4453 setString(&level_info->author, level_author);
4454 level_info->levels = num_levels;
4455 level_info->first_level = 1;
4456 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4457 level_info->readonly = FALSE;
4459 if (use_artwork_set)
4461 level_info->graphics_set =
4462 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4463 level_info->sounds_set =
4464 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4465 level_info->music_set =
4466 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4469 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4471 fprintFileHeader(file, LEVELINFO_FILENAME);
4474 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4476 if (i == LEVELINFO_TOKEN_NAME ||
4477 i == LEVELINFO_TOKEN_AUTHOR ||
4478 i == LEVELINFO_TOKEN_LEVELS ||
4479 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4480 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4481 i == LEVELINFO_TOKEN_READONLY ||
4482 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4483 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4484 i == LEVELINFO_TOKEN_MUSIC_SET)))
4485 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4487 // just to make things nicer :)
4488 if (i == LEVELINFO_TOKEN_AUTHOR ||
4489 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4490 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4491 fprintf(file, "\n");
4494 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4498 SetFilePermissions(filename, PERMS_PRIVATE);
4500 freeTreeInfo(level_info);
4506 static void SaveUserLevelInfo(void)
4508 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4511 char *getSetupValue(int type, void *value)
4513 static char value_string[MAX_LINE_LEN];
4521 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4525 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4529 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4530 *(int *)value == FALSE ? "off" : "on"));
4534 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4537 case TYPE_YES_NO_AUTO:
4538 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4539 *(int *)value == FALSE ? "no" : "yes"));
4543 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4547 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4551 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4555 sprintf(value_string, "%d", *(int *)value);
4559 if (*(char **)value == NULL)
4562 strcpy(value_string, *(char **)value);
4566 sprintf(value_string, "player_%d", *(int *)value + 1);
4570 value_string[0] = '\0';
4574 if (type & TYPE_GHOSTED)
4575 strcpy(value_string, "n/a");
4577 return value_string;
4580 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4584 static char token_string[MAX_LINE_LEN];
4585 int token_type = token_info[token_nr].type;
4586 void *setup_value = token_info[token_nr].value;
4587 char *token_text = token_info[token_nr].text;
4588 char *value_string = getSetupValue(token_type, setup_value);
4590 // build complete token string
4591 sprintf(token_string, "%s%s", prefix, token_text);
4593 // build setup entry line
4594 line = getFormattedSetupEntry(token_string, value_string);
4596 if (token_type == TYPE_KEY_X11)
4598 Key key = *(Key *)setup_value;
4599 char *keyname = getKeyNameFromKey(key);
4601 // add comment, if useful
4602 if (!strEqual(keyname, "(undefined)") &&
4603 !strEqual(keyname, "(unknown)"))
4605 // add at least one whitespace
4607 for (i = strlen(line); i < token_comment_position; i++)
4611 strcat(line, keyname);
4618 static void InitLastPlayedLevels_ParentNode(void)
4620 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4621 LevelDirTree *leveldir_new = NULL;
4623 // check if parent node for last played levels already exists
4624 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4627 leveldir_new = newTreeInfo();
4629 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4631 leveldir_new->level_group = TRUE;
4632 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4634 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4635 setString(&leveldir_new->name, "<< (last played level sets)");
4636 setString(&leveldir_new->name_sorting, leveldir_new->name);
4638 pushTreeInfo(leveldir_top, leveldir_new);
4640 // create node to link back to current level directory
4641 createParentTreeInfoNode(leveldir_new);
4644 void UpdateLastPlayedLevels_TreeInfo(void)
4646 char **last_level_series = setup.level_setup.last_level_series;
4647 LevelDirTree *leveldir_last;
4648 TreeInfo **node_new = NULL;
4651 if (last_level_series[0] == NULL)
4654 InitLastPlayedLevels_ParentNode();
4656 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4657 TOKEN_STR_LAST_LEVEL_SERIES,
4658 TREE_NODE_TYPE_GROUP);
4659 if (leveldir_last == NULL)
4662 node_new = &leveldir_last->node_group->next;
4664 freeTreeInfo(*node_new);
4668 for (i = 0; last_level_series[i] != NULL; i++)
4670 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4671 last_level_series[i]);
4672 if (node_last == NULL)
4675 *node_new = getTreeInfoCopy(node_last); // copy complete node
4677 (*node_new)->node_top = &leveldir_first; // correct top node link
4678 (*node_new)->node_parent = leveldir_last; // correct parent node link
4680 (*node_new)->is_copy = TRUE; // mark entry as node copy
4682 (*node_new)->node_group = NULL;
4683 (*node_new)->next = NULL;
4685 (*node_new)->cl_first = -1; // force setting tree cursor
4687 node_new = &((*node_new)->next);
4691 static void UpdateLastPlayedLevels_List(void)
4693 char **last_level_series = setup.level_setup.last_level_series;
4694 int pos = MAX_LEVELDIR_HISTORY - 1;
4697 // search for potentially already existing entry in list of level sets
4698 for (i = 0; last_level_series[i] != NULL; i++)
4699 if (strEqual(last_level_series[i], leveldir_current->identifier))
4702 // move list of level sets one entry down (using potentially free entry)
4703 for (i = pos; i > 0; i--)
4704 setString(&last_level_series[i], last_level_series[i - 1]);
4706 // put last played level set at top position
4707 setString(&last_level_series[0], leveldir_current->identifier);
4710 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4712 static char *identifier = NULL;
4716 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4718 return NULL; // not used
4722 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4724 TREE_NODE_TYPE_COPY);
4725 return (node_new != NULL ? node_new : node);
4729 void StoreLastPlayedLevels(TreeInfo *node)
4731 StoreOrRestoreLastPlayedLevels(node, TRUE);
4734 void RestoreLastPlayedLevels(TreeInfo **node)
4736 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4739 void LoadLevelSetup_LastSeries(void)
4741 // --------------------------------------------------------------------------
4742 // ~/.<program>/levelsetup.conf
4743 // --------------------------------------------------------------------------
4745 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4746 SetupFileHash *level_setup_hash = NULL;
4750 // always start with reliable default values
4751 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4753 // start with empty history of last played level sets
4754 setString(&setup.level_setup.last_level_series[0], NULL);
4756 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4758 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4760 if (leveldir_current == NULL)
4761 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4764 if ((level_setup_hash = loadSetupFileHash(filename)))
4766 char *last_level_series =
4767 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4769 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4771 if (leveldir_current == NULL)
4772 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4774 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4776 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4777 LevelDirTree *leveldir_last;
4779 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4781 last_level_series = getHashEntry(level_setup_hash, token);
4783 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4785 if (leveldir_last != NULL)
4786 setString(&setup.level_setup.last_level_series[pos++],
4790 setString(&setup.level_setup.last_level_series[pos], NULL);
4792 freeSetupFileHash(level_setup_hash);
4796 Debug("setup", "using default setup values");
4802 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4804 // --------------------------------------------------------------------------
4805 // ~/.<program>/levelsetup.conf
4806 // --------------------------------------------------------------------------
4808 // check if the current level directory structure is available at this point
4809 if (leveldir_current == NULL)
4812 char **last_level_series = setup.level_setup.last_level_series;
4813 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4817 InitUserDataDirectory();
4819 UpdateLastPlayedLevels_List();
4821 if (!(file = fopen(filename, MODE_WRITE)))
4823 Warn("cannot write setup file '%s'", filename);
4830 fprintFileHeader(file, LEVELSETUP_FILENAME);
4832 if (deactivate_last_level_series)
4833 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4835 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4836 leveldir_current->identifier));
4838 for (i = 0; last_level_series[i] != NULL; i++)
4840 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4842 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4844 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4849 SetFilePermissions(filename, PERMS_PRIVATE);
4854 void SaveLevelSetup_LastSeries(void)
4856 SaveLevelSetup_LastSeries_Ext(FALSE);
4859 void SaveLevelSetup_LastSeries_Deactivate(void)
4861 SaveLevelSetup_LastSeries_Ext(TRUE);
4864 static void checkSeriesInfo(void)
4866 static char *level_directory = NULL;
4869 DirectoryEntry *dir_entry;
4872 checked_free(level_directory);
4874 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4876 level_directory = getPath2((leveldir_current->in_user_dir ?
4877 getUserLevelDir(NULL) :
4878 options.level_directory),
4879 leveldir_current->fullpath);
4881 if ((dir = openDirectory(level_directory)) == NULL)
4883 Warn("cannot read level directory '%s'", level_directory);
4889 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4891 if (strlen(dir_entry->basename) > 4 &&
4892 dir_entry->basename[3] == '.' &&
4893 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4895 char levelnum_str[4];
4898 strncpy(levelnum_str, dir_entry->basename, 3);
4899 levelnum_str[3] = '\0';
4901 levelnum_value = atoi(levelnum_str);
4903 if (levelnum_value < leveldir_current->first_level)
4905 Warn("additional level %d found", levelnum_value);
4907 leveldir_current->first_level = levelnum_value;
4909 else if (levelnum_value > leveldir_current->last_level)
4911 Warn("additional level %d found", levelnum_value);
4913 leveldir_current->last_level = levelnum_value;
4919 closeDirectory(dir);
4922 void LoadLevelSetup_SeriesInfo(void)
4925 SetupFileHash *level_setup_hash = NULL;
4926 char *level_subdir = leveldir_current->subdir;
4929 // always start with reliable default values
4930 level_nr = leveldir_current->first_level;
4932 for (i = 0; i < MAX_LEVELS; i++)
4934 LevelStats_setPlayed(i, 0);
4935 LevelStats_setSolved(i, 0);
4940 // --------------------------------------------------------------------------
4941 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4942 // --------------------------------------------------------------------------
4944 level_subdir = leveldir_current->subdir;
4946 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4948 if ((level_setup_hash = loadSetupFileHash(filename)))
4952 // get last played level in this level set
4954 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4958 level_nr = atoi(token_value);
4960 if (level_nr < leveldir_current->first_level)
4961 level_nr = leveldir_current->first_level;
4962 if (level_nr > leveldir_current->last_level)
4963 level_nr = leveldir_current->last_level;
4966 // get handicap level in this level set
4968 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4972 int level_nr = atoi(token_value);
4974 if (level_nr < leveldir_current->first_level)
4975 level_nr = leveldir_current->first_level;
4976 if (level_nr > leveldir_current->last_level + 1)
4977 level_nr = leveldir_current->last_level;
4979 if (leveldir_current->user_defined || !leveldir_current->handicap)
4980 level_nr = leveldir_current->last_level;
4982 leveldir_current->handicap_level = level_nr;
4985 // get number of played and solved levels in this level set
4987 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4989 char *token = HASH_ITERATION_TOKEN(itr);
4990 char *value = HASH_ITERATION_VALUE(itr);
4992 if (strlen(token) == 3 &&
4993 token[0] >= '0' && token[0] <= '9' &&
4994 token[1] >= '0' && token[1] <= '9' &&
4995 token[2] >= '0' && token[2] <= '9')
4997 int level_nr = atoi(token);
5000 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5002 value = strchr(value, ' ');
5005 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5008 END_HASH_ITERATION(hash, itr)
5010 freeSetupFileHash(level_setup_hash);
5014 Debug("setup", "using default setup values");
5020 void SaveLevelSetup_SeriesInfo(void)
5023 char *level_subdir = leveldir_current->subdir;
5024 char *level_nr_str = int2str(level_nr, 0);
5025 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5029 // --------------------------------------------------------------------------
5030 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5031 // --------------------------------------------------------------------------
5033 InitLevelSetupDirectory(level_subdir);
5035 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5037 if (!(file = fopen(filename, MODE_WRITE)))
5039 Warn("cannot write setup file '%s'", filename);
5046 fprintFileHeader(file, LEVELSETUP_FILENAME);
5048 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5050 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5051 handicap_level_str));
5053 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5056 if (LevelStats_getPlayed(i) > 0 ||
5057 LevelStats_getSolved(i) > 0)
5062 sprintf(token, "%03d", i);
5063 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5065 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5071 SetFilePermissions(filename, PERMS_PRIVATE);
5076 int LevelStats_getPlayed(int nr)
5078 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5081 int LevelStats_getSolved(int nr)
5083 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5086 void LevelStats_setPlayed(int nr, int value)
5088 if (nr >= 0 && nr < MAX_LEVELS)
5089 level_stats[nr].played = value;
5092 void LevelStats_setSolved(int nr, int value)
5094 if (nr >= 0 && nr < MAX_LEVELS)
5095 level_stats[nr].solved = value;
5098 void LevelStats_incPlayed(int nr)
5100 if (nr >= 0 && nr < MAX_LEVELS)
5101 level_stats[nr].played++;
5104 void LevelStats_incSolved(int nr)
5106 if (nr >= 0 && nr < MAX_LEVELS)
5107 level_stats[nr].solved++;
5110 void LoadUserSetup(void)
5112 // --------------------------------------------------------------------------
5113 // ~/.<program>/usersetup.conf
5114 // --------------------------------------------------------------------------
5116 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5117 SetupFileHash *user_setup_hash = NULL;
5119 // always start with reliable default values
5122 if ((user_setup_hash = loadSetupFileHash(filename)))
5126 // get last selected user number
5127 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5130 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5132 freeSetupFileHash(user_setup_hash);
5136 Debug("setup", "using default setup values");
5142 void SaveUserSetup(void)
5144 // --------------------------------------------------------------------------
5145 // ~/.<program>/usersetup.conf
5146 // --------------------------------------------------------------------------
5148 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5151 InitMainUserDataDirectory();
5153 if (!(file = fopen(filename, MODE_WRITE)))
5155 Warn("cannot write setup file '%s'", filename);
5162 fprintFileHeader(file, USERSETUP_FILENAME);
5164 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5168 SetFilePermissions(filename, PERMS_PRIVATE);