1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 static char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *getTemporaryTapeFilename(void)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
593 filename = getPath2(getTapeDir(NULL), basename);
598 char *getDefaultSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSokobanSolutionTapeFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.sln", nr);
619 filename = getPath2(getSolutionTapeDir(), basename);
624 char *getSolutionTapeFilename(int nr)
626 char *filename = getDefaultSolutionTapeFilename(nr);
628 if (!fileExists(filename))
630 char *filename2 = getSokobanSolutionTapeFilename(nr);
632 if (fileExists(filename2))
639 char *getScoreFilename(int nr)
641 static char *filename = NULL;
642 char basename[MAX_FILENAME_LEN];
644 checked_free(filename);
646 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
648 // used instead of "leveldir_current->subdir" (for network games)
649 filename = getPath2(getScoreDir(levelset.identifier), basename);
654 char *getScoreCacheFilename(int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
669 char *getScoreTapeBasename(char *name)
671 static char basename[MAX_FILENAME_LEN];
672 char basename_raw[MAX_FILENAME_LEN];
675 sprintf(timestamp, "%s", getCurrentTimestamp());
676 sprintf(basename_raw, "%s-%s", timestamp, name);
677 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
682 char *getScoreTapeFilename(char *basename_no_ext, int nr)
684 static char *filename = NULL;
685 char basename[MAX_FILENAME_LEN];
687 checked_free(filename);
689 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
691 // used instead of "leveldir_current->subdir" (for network games)
692 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
697 char *getSetupFilename(void)
699 static char *filename = NULL;
701 checked_free(filename);
703 filename = getPath2(getSetupDir(), SETUP_FILENAME);
708 char *getDefaultSetupFilename(void)
710 return program.config_filename;
713 char *getEditorSetupFilename(void)
715 static char *filename = NULL;
717 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
720 if (fileExists(filename))
723 checked_free(filename);
724 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
729 char *getHelpAnimFilename(void)
731 static char *filename = NULL;
733 checked_free(filename);
735 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
740 char *getHelpTextFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
751 char *getLevelSetInfoFilename(void)
753 static char *filename = NULL;
768 for (i = 0; basenames[i] != NULL; i++)
770 checked_free(filename);
771 filename = getPath2(getCurrentLevelDir(), basenames[i]);
773 if (fileExists(filename))
780 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
782 static char basename[32];
784 sprintf(basename, "%s_%d.txt",
785 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
790 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
792 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getLevelSetTitleMessageBasename(nr, initial);
800 if (!gfx.override_level_graphics)
802 // 1st try: look for special artwork in current level series directory
803 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
804 if (fileExists(filename))
809 // 2nd try: look for message file in current level set directory
810 filename = getPath2(getCurrentLevelDir(), basename);
811 if (fileExists(filename))
816 // check if there is special artwork configured in level series config
817 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
819 // 3rd try: look for special artwork configured in level series config
820 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
821 if (fileExists(filename))
826 // take missing artwork configured in level set config from default
827 skip_setup_artwork = TRUE;
831 if (!skip_setup_artwork)
833 // 4th try: look for special artwork in configured artwork directory
834 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
835 if (fileExists(filename))
841 // 5th try: look for default artwork in new default artwork directory
842 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
843 if (fileExists(filename))
848 // 6th try: look for default artwork in old default artwork directory
849 filename = getPath2(options.graphics_directory, basename);
850 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 static char *getCorrectedArtworkBasename(char *basename)
861 char *getCustomImageFilename(char *basename)
863 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getCorrectedArtworkBasename(basename);
870 if (!gfx.override_level_graphics)
872 // 1st try: look for special artwork in current level series directory
873 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
874 if (fileExists(filename))
879 // check if there is special artwork configured in level series config
880 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
882 // 2nd try: look for special artwork configured in level series config
883 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
884 if (fileExists(filename))
889 // take missing artwork configured in level set config from default
890 skip_setup_artwork = TRUE;
894 if (!skip_setup_artwork)
896 // 3rd try: look for special artwork in configured artwork directory
897 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
898 if (fileExists(filename))
904 // 4th try: look for default artwork in new default artwork directory
905 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
906 if (fileExists(filename))
911 // 5th try: look for default artwork in old default artwork directory
912 filename = getImg2(options.graphics_directory, basename);
913 if (fileExists(filename))
916 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
920 Warn("cannot find artwork file '%s' (using fallback)", basename);
922 // 6th try: look for fallback artwork in old default artwork directory
923 // (needed to prevent errors when trying to access unused artwork files)
924 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
925 if (fileExists(filename))
929 return NULL; // cannot find specified artwork file anywhere
932 char *getCustomSoundFilename(char *basename)
934 static char *filename = NULL;
935 boolean skip_setup_artwork = FALSE;
937 checked_free(filename);
939 basename = getCorrectedArtworkBasename(basename);
941 if (!gfx.override_level_sounds)
943 // 1st try: look for special artwork in current level series directory
944 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
945 if (fileExists(filename))
950 // check if there is special artwork configured in level series config
951 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
953 // 2nd try: look for special artwork configured in level series config
954 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
955 if (fileExists(filename))
960 // take missing artwork configured in level set config from default
961 skip_setup_artwork = TRUE;
965 if (!skip_setup_artwork)
967 // 3rd try: look for special artwork in configured artwork directory
968 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
969 if (fileExists(filename))
975 // 4th try: look for default artwork in new default artwork directory
976 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
977 if (fileExists(filename))
982 // 5th try: look for default artwork in old default artwork directory
983 filename = getPath2(options.sounds_directory, basename);
984 if (fileExists(filename))
987 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
991 Warn("cannot find artwork file '%s' (using fallback)", basename);
993 // 6th try: look for fallback artwork in old default artwork directory
994 // (needed to prevent errors when trying to access unused artwork files)
995 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
996 if (fileExists(filename))
1000 return NULL; // cannot find specified artwork file anywhere
1003 char *getCustomMusicFilename(char *basename)
1005 static char *filename = NULL;
1006 boolean skip_setup_artwork = FALSE;
1008 checked_free(filename);
1010 basename = getCorrectedArtworkBasename(basename);
1012 if (!gfx.override_level_music)
1014 // 1st try: look for special artwork in current level series directory
1015 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1016 if (fileExists(filename))
1021 // check if there is special artwork configured in level series config
1022 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1024 // 2nd try: look for special artwork configured in level series config
1025 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1026 if (fileExists(filename))
1031 // take missing artwork configured in level set config from default
1032 skip_setup_artwork = TRUE;
1036 if (!skip_setup_artwork)
1038 // 3rd try: look for special artwork in configured artwork directory
1039 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1040 if (fileExists(filename))
1046 // 4th try: look for default artwork in new default artwork directory
1047 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1048 if (fileExists(filename))
1053 // 5th try: look for default artwork in old default artwork directory
1054 filename = getPath2(options.music_directory, basename);
1055 if (fileExists(filename))
1058 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1062 Warn("cannot find artwork file '%s' (using fallback)", basename);
1064 // 6th try: look for fallback artwork in old default artwork directory
1065 // (needed to prevent errors when trying to access unused artwork files)
1066 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1067 if (fileExists(filename))
1071 return NULL; // cannot find specified artwork file anywhere
1074 char *getCustomArtworkFilename(char *basename, int type)
1076 if (type == ARTWORK_TYPE_GRAPHICS)
1077 return getCustomImageFilename(basename);
1078 else if (type == ARTWORK_TYPE_SOUNDS)
1079 return getCustomSoundFilename(basename);
1080 else if (type == ARTWORK_TYPE_MUSIC)
1081 return getCustomMusicFilename(basename);
1083 return UNDEFINED_FILENAME;
1086 char *getCustomArtworkConfigFilename(int type)
1088 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1091 char *getCustomArtworkLevelConfigFilename(int type)
1093 static char *filename = NULL;
1095 checked_free(filename);
1097 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1102 char *getCustomMusicDirectory(void)
1104 static char *directory = NULL;
1105 boolean skip_setup_artwork = FALSE;
1107 checked_free(directory);
1109 if (!gfx.override_level_music)
1111 // 1st try: look for special artwork in current level series directory
1112 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1113 if (directoryExists(directory))
1118 // check if there is special artwork configured in level series config
1119 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1121 // 2nd try: look for special artwork configured in level series config
1122 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1123 if (directoryExists(directory))
1128 // take missing artwork configured in level set config from default
1129 skip_setup_artwork = TRUE;
1133 if (!skip_setup_artwork)
1135 // 3rd try: look for special artwork in configured artwork directory
1136 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1137 if (directoryExists(directory))
1143 // 4th try: look for default artwork in new default artwork directory
1144 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1145 if (directoryExists(directory))
1150 // 5th try: look for default artwork in old default artwork directory
1151 directory = getStringCopy(options.music_directory);
1152 if (directoryExists(directory))
1155 return NULL; // cannot find specified artwork file anywhere
1158 void InitTapeDirectory(char *level_subdir)
1160 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1161 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1162 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1165 void InitScoreDirectory(char *level_subdir)
1167 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1168 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1169 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1172 void InitScoreCacheDirectory(char *level_subdir)
1174 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1175 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1176 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1177 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1180 void InitScoreTapeDirectory(char *level_subdir, int nr)
1182 InitScoreDirectory(level_subdir);
1184 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1187 static void SaveUserLevelInfo(void);
1189 void InitUserLevelDirectory(char *level_subdir)
1191 if (!directoryExists(getUserLevelDir(level_subdir)))
1193 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1194 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1195 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1197 if (setup.internal.create_user_levelset)
1198 SaveUserLevelInfo();
1202 void InitNetworkLevelDirectory(char *level_subdir)
1204 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1206 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1207 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1208 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1209 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1213 void InitLevelSetupDirectory(char *level_subdir)
1215 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1216 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1217 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1220 static void InitCacheDirectory(void)
1222 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1223 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1227 // ----------------------------------------------------------------------------
1228 // some functions to handle lists of level and artwork directories
1229 // ----------------------------------------------------------------------------
1231 TreeInfo *newTreeInfo(void)
1233 return checked_calloc(sizeof(TreeInfo));
1236 TreeInfo *newTreeInfo_setDefaults(int type)
1238 TreeInfo *ti = newTreeInfo();
1240 setTreeInfoToDefaults(ti, type);
1245 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1247 node_new->next = *node_first;
1248 *node_first = node_new;
1251 void removeTreeInfo(TreeInfo **node_first)
1253 TreeInfo *node_old = *node_first;
1255 *node_first = node_old->next;
1256 node_old->next = NULL;
1258 freeTreeInfo(node_old);
1261 int numTreeInfo(TreeInfo *node)
1274 boolean validLevelSeries(TreeInfo *node)
1276 // in a number of cases, tree node is no valid level set
1277 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1283 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1285 if (validLevelSeries(node))
1287 else if (node->is_copy)
1288 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1290 return getFirstValidTreeInfoEntry(default_node);
1293 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1298 if (node->node_group) // enter level group (step down into tree)
1299 return getFirstValidTreeInfoEntry(node->node_group);
1300 else if (node->parent_link) // skip start entry of level group
1302 if (node->next) // get first real level series entry
1303 return getFirstValidTreeInfoEntry(node->next);
1304 else // leave empty level group and go on
1305 return getFirstValidTreeInfoEntry(node->node_parent->next);
1307 else // this seems to be a regular level series
1311 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1316 if (node->node_parent == NULL) // top level group
1317 return *node->node_top;
1318 else // sub level group
1319 return node->node_parent->node_group;
1322 int numTreeInfoInGroup(TreeInfo *node)
1324 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1327 int getPosFromTreeInfo(TreeInfo *node)
1329 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1334 if (node_cmp == node)
1338 node_cmp = node_cmp->next;
1344 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1346 TreeInfo *node_default = node;
1358 return node_default;
1361 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1362 int node_type_wanted)
1364 if (identifier == NULL)
1369 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1370 strEqual(identifier, node->identifier))
1373 if (node->node_group)
1375 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1388 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1390 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1393 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1394 TreeInfo *node, boolean skip_sets_without_levels)
1401 if (!node->parent_link && !node->level_group &&
1402 skip_sets_without_levels && node->levels == 0)
1403 return cloneTreeNode(node_top, node_parent, node->next,
1404 skip_sets_without_levels);
1406 node_new = getTreeInfoCopy(node); // copy complete node
1408 node_new->node_top = node_top; // correct top node link
1409 node_new->node_parent = node_parent; // correct parent node link
1411 if (node->level_group)
1412 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1413 skip_sets_without_levels);
1415 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1416 skip_sets_without_levels);
1421 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1423 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1425 *ti_new = ti_cloned;
1428 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1430 boolean settings_changed = FALSE;
1434 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1435 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1436 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1437 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1438 char *graphics_set = NULL;
1440 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1441 graphics_set = node->graphics_set_ecs;
1443 if (node->graphics_set_aga && (want_aga || has_only_aga))
1444 graphics_set = node->graphics_set_aga;
1446 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1448 setString(&node->graphics_set, graphics_set);
1449 settings_changed = TRUE;
1452 if (node->node_group != NULL)
1453 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1458 return settings_changed;
1461 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1463 boolean settings_changed = FALSE;
1467 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1468 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1469 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1470 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1471 char *sounds_set = NULL;
1473 if (node->sounds_set_default && (want_default || has_only_default))
1474 sounds_set = node->sounds_set_default;
1476 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1477 sounds_set = node->sounds_set_lowpass;
1479 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1481 setString(&node->sounds_set, sounds_set);
1482 settings_changed = TRUE;
1485 if (node->node_group != NULL)
1486 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1491 return settings_changed;
1494 int dumpTreeInfo(TreeInfo *node, int depth)
1496 char bullet_list[] = { '-', '*', 'o' };
1497 int num_leaf_nodes = 0;
1501 Debug("tree", "Dumping TreeInfo:");
1505 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1507 for (i = 0; i < depth * 2; i++)
1508 DebugContinued("", " ");
1510 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1511 bullet, node->name, node->identifier,
1512 (node->node_parent ? node->node_parent->identifier : "-"),
1513 (node->node_group ? "[GROUP]" : ""));
1515 if (!node->node_group && !node->parent_link)
1519 // use for dumping artwork info tree
1520 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1521 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1524 if (node->node_group != NULL)
1525 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1531 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1533 return num_leaf_nodes;
1536 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1537 int (*compare_function)(const void *,
1540 int num_nodes = numTreeInfo(*node_first);
1541 TreeInfo **sort_array;
1542 TreeInfo *node = *node_first;
1548 // allocate array for sorting structure pointers
1549 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1551 // writing structure pointers to sorting array
1552 while (i < num_nodes && node) // double boundary check...
1554 sort_array[i] = node;
1560 // sorting the structure pointers in the sorting array
1561 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1564 // update the linkage of list elements with the sorted node array
1565 for (i = 0; i < num_nodes - 1; i++)
1566 sort_array[i]->next = sort_array[i + 1];
1567 sort_array[num_nodes - 1]->next = NULL;
1569 // update the linkage of the main list anchor pointer
1570 *node_first = sort_array[0];
1574 // now recursively sort the level group structures
1578 if (node->node_group != NULL)
1579 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1585 void sortTreeInfo(TreeInfo **node_first)
1587 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1591 // ============================================================================
1592 // some stuff from "files.c"
1593 // ============================================================================
1595 #if defined(PLATFORM_WIN32)
1597 #define S_IRGRP S_IRUSR
1600 #define S_IROTH S_IRUSR
1603 #define S_IWGRP S_IWUSR
1606 #define S_IWOTH S_IWUSR
1609 #define S_IXGRP S_IXUSR
1612 #define S_IXOTH S_IXUSR
1615 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1620 #endif // PLATFORM_WIN32
1622 // file permissions for newly written files
1623 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1624 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1625 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1627 #define MODE_W_PRIVATE (S_IWUSR)
1628 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1629 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1631 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1632 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1633 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1635 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1636 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1637 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1640 char *getHomeDir(void)
1642 static char *dir = NULL;
1644 #if defined(PLATFORM_WIN32)
1647 dir = checked_malloc(MAX_PATH + 1);
1649 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1652 #elif defined(PLATFORM_EMSCRIPTEN)
1653 dir = "/persistent";
1654 #elif defined(PLATFORM_UNIX)
1657 if ((dir = getenv("HOME")) == NULL)
1659 dir = getUnixHomeDir();
1662 dir = getStringCopy(dir);
1674 char *getPersonalDataDir(void)
1676 static char *personal_data_dir = NULL;
1678 #if defined(PLATFORM_MACOSX)
1679 if (personal_data_dir == NULL)
1680 personal_data_dir = getPath2(getHomeDir(), "Documents");
1682 if (personal_data_dir == NULL)
1683 personal_data_dir = getHomeDir();
1686 return personal_data_dir;
1689 char *getMainUserGameDataDir(void)
1691 static char *main_user_data_dir = NULL;
1693 #if defined(PLATFORM_ANDROID)
1694 if (main_user_data_dir == NULL)
1695 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1696 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1697 SDL_AndroidGetExternalStoragePath() :
1698 SDL_AndroidGetInternalStoragePath());
1700 if (main_user_data_dir == NULL)
1701 main_user_data_dir = getPath2(getPersonalDataDir(),
1702 program.userdata_subdir);
1705 return main_user_data_dir;
1708 char *getUserGameDataDir(void)
1711 return getMainUserGameDataDir();
1713 return getUserDir(user.nr);
1716 char *getSetupDir(void)
1718 return getUserGameDataDir();
1721 static mode_t posix_umask(mode_t mask)
1723 #if defined(PLATFORM_UNIX)
1730 static int posix_mkdir(const char *pathname, mode_t mode)
1732 #if defined(PLATFORM_WIN32)
1733 return mkdir(pathname);
1735 return mkdir(pathname, mode);
1739 static boolean posix_process_running_setgid(void)
1741 #if defined(PLATFORM_UNIX)
1742 return (getgid() != getegid());
1748 void createDirectory(char *dir, char *text, int permission_class)
1750 if (directoryExists(dir))
1753 // leave "other" permissions in umask untouched, but ensure group parts
1754 // of USERDATA_DIR_MODE are not masked
1755 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1756 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1757 mode_t last_umask = posix_umask(0);
1758 mode_t group_umask = ~(dir_mode & S_IRWXG);
1759 int running_setgid = posix_process_running_setgid();
1761 if (permission_class == PERMS_PUBLIC)
1763 // if we're setgid, protect files against "other"
1764 // else keep umask(0) to make the dir world-writable
1767 posix_umask(last_umask & group_umask);
1769 dir_mode = DIR_PERMS_PUBLIC_ALL;
1772 if (posix_mkdir(dir, dir_mode) != 0)
1773 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1775 if (permission_class == PERMS_PUBLIC && !running_setgid)
1776 chmod(dir, dir_mode);
1778 posix_umask(last_umask); // restore previous umask
1781 void InitMainUserDataDirectory(void)
1783 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1786 void InitUserDataDirectory(void)
1788 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1792 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1793 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1797 void SetFilePermissions(char *filename, int permission_class)
1799 int running_setgid = posix_process_running_setgid();
1800 int perms = (permission_class == PERMS_PRIVATE ?
1801 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1803 if (permission_class == PERMS_PUBLIC && !running_setgid)
1804 perms = FILE_PERMS_PUBLIC_ALL;
1806 chmod(filename, perms);
1809 char *getCookie(char *file_type)
1811 static char cookie[MAX_COOKIE_LEN + 1];
1813 if (strlen(program.cookie_prefix) + 1 +
1814 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1815 return "[COOKIE ERROR]"; // should never happen
1817 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1818 program.cookie_prefix, file_type,
1819 program.version_super, program.version_major);
1824 void fprintFileHeader(FILE *file, char *basename)
1826 char *prefix = "# ";
1829 fprintf_line_with_prefix(file, prefix, sep1, 77);
1830 fprintf(file, "%s%s\n", prefix, basename);
1831 fprintf_line_with_prefix(file, prefix, sep1, 77);
1832 fprintf(file, "\n");
1835 int getFileVersionFromCookieString(const char *cookie)
1837 const char *ptr_cookie1, *ptr_cookie2;
1838 const char *pattern1 = "_FILE_VERSION_";
1839 const char *pattern2 = "?.?";
1840 const int len_cookie = strlen(cookie);
1841 const int len_pattern1 = strlen(pattern1);
1842 const int len_pattern2 = strlen(pattern2);
1843 const int len_pattern = len_pattern1 + len_pattern2;
1844 int version_super, version_major;
1846 if (len_cookie <= len_pattern)
1849 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1850 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1852 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1855 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1856 ptr_cookie2[1] != '.' ||
1857 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1860 version_super = ptr_cookie2[0] - '0';
1861 version_major = ptr_cookie2[2] - '0';
1863 return VERSION_IDENT(version_super, version_major, 0, 0);
1866 boolean checkCookieString(const char *cookie, const char *template)
1868 const char *pattern = "_FILE_VERSION_?.?";
1869 const int len_cookie = strlen(cookie);
1870 const int len_template = strlen(template);
1871 const int len_pattern = strlen(pattern);
1873 if (len_cookie != len_template)
1876 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1883 // ----------------------------------------------------------------------------
1884 // setup file list and hash handling functions
1885 // ----------------------------------------------------------------------------
1887 char *getFormattedSetupEntry(char *token, char *value)
1890 static char entry[MAX_LINE_LEN];
1892 // if value is an empty string, just return token without value
1896 // start with the token and some spaces to format output line
1897 sprintf(entry, "%s:", token);
1898 for (i = strlen(entry); i < token_value_position; i++)
1901 // continue with the token's value
1902 strcat(entry, value);
1907 SetupFileList *newSetupFileList(char *token, char *value)
1909 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1911 new->token = getStringCopy(token);
1912 new->value = getStringCopy(value);
1919 void freeSetupFileList(SetupFileList *list)
1924 checked_free(list->token);
1925 checked_free(list->value);
1928 freeSetupFileList(list->next);
1933 char *getListEntry(SetupFileList *list, char *token)
1938 if (strEqual(list->token, token))
1941 return getListEntry(list->next, token);
1944 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1949 if (strEqual(list->token, token))
1951 checked_free(list->value);
1953 list->value = getStringCopy(value);
1957 else if (list->next == NULL)
1958 return (list->next = newSetupFileList(token, value));
1960 return setListEntry(list->next, token, value);
1963 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1968 if (list->next == NULL)
1969 return (list->next = newSetupFileList(token, value));
1971 return addListEntry(list->next, token, value);
1974 #if ENABLE_UNUSED_CODE
1976 static void printSetupFileList(SetupFileList *list)
1981 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1982 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1984 printSetupFileList(list->next);
1990 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1991 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1992 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1993 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1995 #define insert_hash_entry hashtable_insert
1996 #define search_hash_entry hashtable_search
1997 #define change_hash_entry hashtable_change
1998 #define remove_hash_entry hashtable_remove
2001 unsigned int get_hash_from_key(void *key)
2006 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2007 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2008 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2009 it works better than many other constants, prime or not) has never been
2010 adequately explained.
2012 If you just want to have a good hash function, and cannot wait, djb2
2013 is one of the best string hash functions i know. It has excellent
2014 distribution and speed on many different sets of keys and table sizes.
2015 You are not likely to do better with one of the "well known" functions
2016 such as PJW, K&R, etc.
2018 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2021 char *str = (char *)key;
2022 unsigned int hash = 5381;
2025 while ((c = *str++))
2026 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2031 static int keys_are_equal(void *key1, void *key2)
2033 return (strEqual((char *)key1, (char *)key2));
2036 SetupFileHash *newSetupFileHash(void)
2038 SetupFileHash *new_hash =
2039 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
2041 if (new_hash == NULL)
2042 Fail("create_hashtable() failed -- out of memory");
2047 void freeSetupFileHash(SetupFileHash *hash)
2052 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2055 char *getHashEntry(SetupFileHash *hash, char *token)
2060 return search_hash_entry(hash, token);
2063 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2070 value_copy = getStringCopy(value);
2072 // change value; if it does not exist, insert it as new
2073 if (!change_hash_entry(hash, token, value_copy))
2074 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2075 Fail("cannot insert into hash -- aborting");
2078 char *removeHashEntry(SetupFileHash *hash, char *token)
2083 return remove_hash_entry(hash, token);
2086 #if ENABLE_UNUSED_CODE
2088 static void printSetupFileHash(SetupFileHash *hash)
2090 BEGIN_HASH_ITERATION(hash, itr)
2092 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2093 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2095 END_HASH_ITERATION(hash, itr)
2100 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2101 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2102 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2104 static boolean token_value_separator_found = FALSE;
2105 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2106 static boolean token_value_separator_warning = FALSE;
2108 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2109 static boolean token_already_exists_warning = FALSE;
2112 static boolean getTokenValueFromSetupLineExt(char *line,
2113 char **token_ptr, char **value_ptr,
2114 char *filename, char *line_raw,
2116 boolean separator_required)
2118 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2119 char *token, *value, *line_ptr;
2121 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2122 if (line_raw == NULL)
2124 strncpy(line_copy, line, MAX_LINE_LEN);
2125 line_copy[MAX_LINE_LEN] = '\0';
2128 strcpy(line_raw_copy, line_copy);
2129 line_raw = line_raw_copy;
2132 // cut trailing comment from input line
2133 for (line_ptr = line; *line_ptr; line_ptr++)
2135 if (*line_ptr == '#')
2142 // cut trailing whitespaces from input line
2143 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2144 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2147 // ignore empty lines
2151 // cut leading whitespaces from token
2152 for (token = line; *token; token++)
2153 if (*token != ' ' && *token != '\t')
2156 // start with empty value as reliable default
2159 token_value_separator_found = FALSE;
2161 // find end of token to determine start of value
2162 for (line_ptr = token; *line_ptr; line_ptr++)
2164 // first look for an explicit token/value separator, like ':' or '='
2165 if (*line_ptr == ':' || *line_ptr == '=')
2167 *line_ptr = '\0'; // terminate token string
2168 value = line_ptr + 1; // set beginning of value
2170 token_value_separator_found = TRUE;
2176 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2177 // fallback: if no token/value separator found, also allow whitespaces
2178 if (!token_value_separator_found && !separator_required)
2180 for (line_ptr = token; *line_ptr; line_ptr++)
2182 if (*line_ptr == ' ' || *line_ptr == '\t')
2184 *line_ptr = '\0'; // terminate token string
2185 value = line_ptr + 1; // set beginning of value
2187 token_value_separator_found = TRUE;
2193 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2194 if (token_value_separator_found)
2196 if (!token_value_separator_warning)
2198 Debug("setup", "---");
2200 if (filename != NULL)
2202 Debug("setup", "missing token/value separator(s) in config file:");
2203 Debug("setup", "- config file: '%s'", filename);
2207 Debug("setup", "missing token/value separator(s):");
2210 token_value_separator_warning = TRUE;
2213 if (filename != NULL)
2214 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2216 Debug("setup", "- line: '%s'", line_raw);
2222 // cut trailing whitespaces from token
2223 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2224 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2227 // cut leading whitespaces from value
2228 for (; *value; value++)
2229 if (*value != ' ' && *value != '\t')
2238 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2240 // while the internal (old) interface does not require a token/value
2241 // separator (for downwards compatibility with existing files which
2242 // don't use them), it is mandatory for the external (new) interface
2244 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2247 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2248 boolean top_recursion_level, boolean is_hash)
2250 static SetupFileHash *include_filename_hash = NULL;
2251 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2252 char *token, *value, *line_ptr;
2253 void *insert_ptr = NULL;
2254 boolean read_continued_line = FALSE;
2256 int line_nr = 0, token_count = 0, include_count = 0;
2258 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2259 token_value_separator_warning = FALSE;
2262 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2263 token_already_exists_warning = FALSE;
2266 if (!(file = openFile(filename, MODE_READ)))
2268 #if DEBUG_NO_CONFIG_FILE
2269 Debug("setup", "cannot open configuration file '%s'", filename);
2275 // use "insert pointer" to store list end for constant insertion complexity
2277 insert_ptr = setup_file_data;
2279 // on top invocation, create hash to mark included files (to prevent loops)
2280 if (top_recursion_level)
2281 include_filename_hash = newSetupFileHash();
2283 // mark this file as already included (to prevent including it again)
2284 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2286 while (!checkEndOfFile(file))
2288 // read next line of input file
2289 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2292 // check if line was completely read and is terminated by line break
2293 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2296 // cut trailing line break (this can be newline and/or carriage return)
2297 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2298 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2301 // copy raw input line for later use (mainly debugging output)
2302 strcpy(line_raw, line);
2304 if (read_continued_line)
2306 // append new line to existing line, if there is enough space
2307 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2308 strcat(previous_line, line_ptr);
2310 strcpy(line, previous_line); // copy storage buffer to line
2312 read_continued_line = FALSE;
2315 // if the last character is '\', continue at next line
2316 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2318 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2319 strcpy(previous_line, line); // copy line to storage buffer
2321 read_continued_line = TRUE;
2326 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2327 line_raw, line_nr, FALSE))
2332 if (strEqual(token, "include"))
2334 if (getHashEntry(include_filename_hash, value) == NULL)
2336 char *basepath = getBasePath(filename);
2337 char *basename = getBaseName(value);
2338 char *filename_include = getPath2(basepath, basename);
2340 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2344 free(filename_include);
2350 Warn("ignoring already processed file '%s'", value);
2357 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2359 getHashEntry((SetupFileHash *)setup_file_data, token);
2361 if (old_value != NULL)
2363 if (!token_already_exists_warning)
2365 Debug("setup", "---");
2366 Debug("setup", "duplicate token(s) found in config file:");
2367 Debug("setup", "- config file: '%s'", filename);
2369 token_already_exists_warning = TRUE;
2372 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2373 Debug("setup", " old value: '%s'", old_value);
2374 Debug("setup", " new value: '%s'", value);
2378 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2382 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2392 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2393 if (token_value_separator_warning)
2394 Debug("setup", "---");
2397 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2398 if (token_already_exists_warning)
2399 Debug("setup", "---");
2402 if (token_count == 0 && include_count == 0)
2403 Warn("configuration file '%s' is empty", filename);
2405 if (top_recursion_level)
2406 freeSetupFileHash(include_filename_hash);
2411 static int compareSetupFileData(const void *object1, const void *object2)
2413 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2414 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2416 return strcmp(entry1->token, entry2->token);
2419 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2421 int item_count = hashtable_count(hash);
2422 int item_size = sizeof(struct ConfigInfo);
2423 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2427 // copy string pointers from hash to array
2428 BEGIN_HASH_ITERATION(hash, itr)
2430 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2431 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2435 if (i > item_count) // should never happen
2438 END_HASH_ITERATION(hash, itr)
2440 // sort string pointers from hash in array
2441 qsort(sort_array, item_count, item_size, compareSetupFileData);
2443 if (!(file = fopen(filename, MODE_WRITE)))
2445 Warn("cannot write configuration file '%s'", filename);
2450 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2451 program.version_string));
2452 for (i = 0; i < item_count; i++)
2453 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2454 sort_array[i].value));
2457 checked_free(sort_array);
2460 SetupFileList *loadSetupFileList(char *filename)
2462 SetupFileList *setup_file_list = newSetupFileList("", "");
2463 SetupFileList *first_valid_list_entry;
2465 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2467 freeSetupFileList(setup_file_list);
2472 first_valid_list_entry = setup_file_list->next;
2474 // free empty list header
2475 setup_file_list->next = NULL;
2476 freeSetupFileList(setup_file_list);
2478 return first_valid_list_entry;
2481 SetupFileHash *loadSetupFileHash(char *filename)
2483 SetupFileHash *setup_file_hash = newSetupFileHash();
2485 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2487 freeSetupFileHash(setup_file_hash);
2492 return setup_file_hash;
2496 // ============================================================================
2498 // ============================================================================
2500 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2501 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2502 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2503 #define TOKEN_STR_LAST_USER "last_user"
2505 // level directory info
2506 #define LEVELINFO_TOKEN_IDENTIFIER 0
2507 #define LEVELINFO_TOKEN_NAME 1
2508 #define LEVELINFO_TOKEN_NAME_SORTING 2
2509 #define LEVELINFO_TOKEN_AUTHOR 3
2510 #define LEVELINFO_TOKEN_YEAR 4
2511 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2512 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2513 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2514 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2515 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2516 #define LEVELINFO_TOKEN_TESTED_BY 10
2517 #define LEVELINFO_TOKEN_LEVELS 11
2518 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2519 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2520 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2521 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2522 #define LEVELINFO_TOKEN_READONLY 16
2523 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2524 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2525 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2526 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2527 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2528 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2529 #define LEVELINFO_TOKEN_MUSIC_SET 23
2530 #define LEVELINFO_TOKEN_FILENAME 24
2531 #define LEVELINFO_TOKEN_FILETYPE 25
2532 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2533 #define LEVELINFO_TOKEN_HANDICAP 27
2534 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2535 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2537 #define NUM_LEVELINFO_TOKENS 30
2539 static LevelDirTree ldi;
2541 static struct TokenInfo levelinfo_tokens[] =
2543 // level directory info
2544 { TYPE_STRING, &ldi.identifier, "identifier" },
2545 { TYPE_STRING, &ldi.name, "name" },
2546 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2547 { TYPE_STRING, &ldi.author, "author" },
2548 { TYPE_STRING, &ldi.year, "year" },
2549 { TYPE_STRING, &ldi.program_title, "program_title" },
2550 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2551 { TYPE_STRING, &ldi.program_company, "program_company" },
2552 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2553 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2554 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2555 { TYPE_INTEGER, &ldi.levels, "levels" },
2556 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2557 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2558 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2559 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2560 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2561 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2562 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2563 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2564 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2565 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2566 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2567 { TYPE_STRING, &ldi.music_set, "music_set" },
2568 { TYPE_STRING, &ldi.level_filename, "filename" },
2569 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2570 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2571 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2572 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2573 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2576 static struct TokenInfo artworkinfo_tokens[] =
2578 // artwork directory info
2579 { TYPE_STRING, &ldi.identifier, "identifier" },
2580 { TYPE_STRING, &ldi.subdir, "subdir" },
2581 { TYPE_STRING, &ldi.name, "name" },
2582 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2583 { TYPE_STRING, &ldi.author, "author" },
2584 { TYPE_STRING, &ldi.program_title, "program_title" },
2585 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2586 { TYPE_STRING, &ldi.program_company, "program_company" },
2587 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2588 { TYPE_STRING, &ldi.basepath, "basepath" },
2589 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2590 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2591 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2596 static char *optional_tokens[] =
2599 "program_copyright",
2605 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2609 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2610 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2611 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2612 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2615 ti->node_parent = NULL;
2616 ti->node_group = NULL;
2623 ti->fullpath = NULL;
2624 ti->basepath = NULL;
2625 ti->identifier = NULL;
2626 ti->name = getStringCopy(ANONYMOUS_NAME);
2627 ti->name_sorting = NULL;
2628 ti->author = getStringCopy(ANONYMOUS_NAME);
2631 ti->program_title = NULL;
2632 ti->program_copyright = NULL;
2633 ti->program_company = NULL;
2635 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2636 ti->latest_engine = FALSE; // default: get from level
2637 ti->parent_link = FALSE;
2638 ti->is_copy = FALSE;
2639 ti->in_user_dir = FALSE;
2640 ti->user_defined = FALSE;
2642 ti->class_desc = NULL;
2644 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2646 if (ti->type == TREE_TYPE_LEVEL_DIR)
2648 ti->imported_from = NULL;
2649 ti->imported_by = NULL;
2650 ti->tested_by = NULL;
2652 ti->graphics_set_ecs = NULL;
2653 ti->graphics_set_aga = NULL;
2654 ti->graphics_set = NULL;
2655 ti->sounds_set_default = NULL;
2656 ti->sounds_set_lowpass = NULL;
2657 ti->sounds_set = NULL;
2658 ti->music_set = NULL;
2659 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2660 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2661 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2663 ti->level_filename = NULL;
2664 ti->level_filetype = NULL;
2666 ti->special_flags = NULL;
2669 ti->first_level = 0;
2671 ti->level_group = FALSE;
2672 ti->handicap_level = 0;
2673 ti->readonly = TRUE;
2674 ti->handicap = TRUE;
2675 ti->skip_levels = FALSE;
2677 ti->use_emc_tiles = FALSE;
2681 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2685 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2687 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2692 // copy all values from the parent structure
2694 ti->type = parent->type;
2696 ti->node_top = parent->node_top;
2697 ti->node_parent = parent;
2698 ti->node_group = NULL;
2705 ti->fullpath = NULL;
2706 ti->basepath = NULL;
2707 ti->identifier = NULL;
2708 ti->name = getStringCopy(ANONYMOUS_NAME);
2709 ti->name_sorting = NULL;
2710 ti->author = getStringCopy(parent->author);
2711 ti->year = getStringCopy(parent->year);
2713 ti->program_title = getStringCopy(parent->program_title);
2714 ti->program_copyright = getStringCopy(parent->program_copyright);
2715 ti->program_company = getStringCopy(parent->program_company);
2717 ti->sort_priority = parent->sort_priority;
2718 ti->latest_engine = parent->latest_engine;
2719 ti->parent_link = FALSE;
2720 ti->is_copy = FALSE;
2721 ti->in_user_dir = parent->in_user_dir;
2722 ti->user_defined = parent->user_defined;
2723 ti->color = parent->color;
2724 ti->class_desc = getStringCopy(parent->class_desc);
2726 ti->infotext = getStringCopy(parent->infotext);
2728 if (ti->type == TREE_TYPE_LEVEL_DIR)
2730 ti->imported_from = getStringCopy(parent->imported_from);
2731 ti->imported_by = getStringCopy(parent->imported_by);
2732 ti->tested_by = getStringCopy(parent->tested_by);
2734 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2735 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2736 ti->graphics_set = getStringCopy(parent->graphics_set);
2737 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2738 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2739 ti->sounds_set = getStringCopy(parent->sounds_set);
2740 ti->music_set = getStringCopy(parent->music_set);
2741 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2742 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2743 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2745 ti->level_filename = getStringCopy(parent->level_filename);
2746 ti->level_filetype = getStringCopy(parent->level_filetype);
2748 ti->special_flags = getStringCopy(parent->special_flags);
2750 ti->levels = parent->levels;
2751 ti->first_level = parent->first_level;
2752 ti->last_level = parent->last_level;
2753 ti->level_group = FALSE;
2754 ti->handicap_level = parent->handicap_level;
2755 ti->readonly = parent->readonly;
2756 ti->handicap = parent->handicap;
2757 ti->skip_levels = parent->skip_levels;
2759 ti->use_emc_tiles = parent->use_emc_tiles;
2763 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2765 TreeInfo *ti_copy = newTreeInfo();
2767 // copy all values from the original structure
2769 ti_copy->type = ti->type;
2771 ti_copy->node_top = ti->node_top;
2772 ti_copy->node_parent = ti->node_parent;
2773 ti_copy->node_group = ti->node_group;
2774 ti_copy->next = ti->next;
2776 ti_copy->cl_first = ti->cl_first;
2777 ti_copy->cl_cursor = ti->cl_cursor;
2779 ti_copy->subdir = getStringCopy(ti->subdir);
2780 ti_copy->fullpath = getStringCopy(ti->fullpath);
2781 ti_copy->basepath = getStringCopy(ti->basepath);
2782 ti_copy->identifier = getStringCopy(ti->identifier);
2783 ti_copy->name = getStringCopy(ti->name);
2784 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2785 ti_copy->author = getStringCopy(ti->author);
2786 ti_copy->year = getStringCopy(ti->year);
2788 ti_copy->program_title = getStringCopy(ti->program_title);
2789 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2790 ti_copy->program_company = getStringCopy(ti->program_company);
2792 ti_copy->imported_from = getStringCopy(ti->imported_from);
2793 ti_copy->imported_by = getStringCopy(ti->imported_by);
2794 ti_copy->tested_by = getStringCopy(ti->tested_by);
2796 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2797 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2798 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2799 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2800 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2801 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2802 ti_copy->music_set = getStringCopy(ti->music_set);
2803 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2804 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2805 ti_copy->music_path = getStringCopy(ti->music_path);
2807 ti_copy->level_filename = getStringCopy(ti->level_filename);
2808 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2810 ti_copy->special_flags = getStringCopy(ti->special_flags);
2812 ti_copy->levels = ti->levels;
2813 ti_copy->first_level = ti->first_level;
2814 ti_copy->last_level = ti->last_level;
2815 ti_copy->sort_priority = ti->sort_priority;
2817 ti_copy->latest_engine = ti->latest_engine;
2819 ti_copy->level_group = ti->level_group;
2820 ti_copy->parent_link = ti->parent_link;
2821 ti_copy->is_copy = ti->is_copy;
2822 ti_copy->in_user_dir = ti->in_user_dir;
2823 ti_copy->user_defined = ti->user_defined;
2824 ti_copy->readonly = ti->readonly;
2825 ti_copy->handicap = ti->handicap;
2826 ti_copy->skip_levels = ti->skip_levels;
2828 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2830 ti_copy->color = ti->color;
2831 ti_copy->class_desc = getStringCopy(ti->class_desc);
2832 ti_copy->handicap_level = ti->handicap_level;
2834 ti_copy->infotext = getStringCopy(ti->infotext);
2839 void freeTreeInfo(TreeInfo *ti)
2844 checked_free(ti->subdir);
2845 checked_free(ti->fullpath);
2846 checked_free(ti->basepath);
2847 checked_free(ti->identifier);
2849 checked_free(ti->name);
2850 checked_free(ti->name_sorting);
2851 checked_free(ti->author);
2852 checked_free(ti->year);
2854 checked_free(ti->program_title);
2855 checked_free(ti->program_copyright);
2856 checked_free(ti->program_company);
2858 checked_free(ti->class_desc);
2860 checked_free(ti->infotext);
2862 if (ti->type == TREE_TYPE_LEVEL_DIR)
2864 checked_free(ti->imported_from);
2865 checked_free(ti->imported_by);
2866 checked_free(ti->tested_by);
2868 checked_free(ti->graphics_set_ecs);
2869 checked_free(ti->graphics_set_aga);
2870 checked_free(ti->graphics_set);
2871 checked_free(ti->sounds_set_default);
2872 checked_free(ti->sounds_set_lowpass);
2873 checked_free(ti->sounds_set);
2874 checked_free(ti->music_set);
2876 checked_free(ti->graphics_path);
2877 checked_free(ti->sounds_path);
2878 checked_free(ti->music_path);
2880 checked_free(ti->level_filename);
2881 checked_free(ti->level_filetype);
2883 checked_free(ti->special_flags);
2886 // recursively free child node
2888 freeTreeInfo(ti->node_group);
2890 // recursively free next node
2892 freeTreeInfo(ti->next);
2897 void setSetupInfo(struct TokenInfo *token_info,
2898 int token_nr, char *token_value)
2900 int token_type = token_info[token_nr].type;
2901 void *setup_value = token_info[token_nr].value;
2903 if (token_value == NULL)
2906 // set setup field to corresponding token value
2911 *(boolean *)setup_value = get_boolean_from_string(token_value);
2915 *(int *)setup_value = get_switch3_from_string(token_value);
2919 *(Key *)setup_value = getKeyFromKeyName(token_value);
2923 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2927 *(int *)setup_value = get_integer_from_string(token_value);
2931 checked_free(*(char **)setup_value);
2932 *(char **)setup_value = getStringCopy(token_value);
2936 *(int *)setup_value = get_player_nr_from_string(token_value);
2944 static int compareTreeInfoEntries(const void *object1, const void *object2)
2946 const TreeInfo *entry1 = *((TreeInfo **)object1);
2947 const TreeInfo *entry2 = *((TreeInfo **)object2);
2948 int tree_sorting1 = TREE_SORTING(entry1);
2949 int tree_sorting2 = TREE_SORTING(entry2);
2951 if (tree_sorting1 != tree_sorting2)
2952 return (tree_sorting1 - tree_sorting2);
2954 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2957 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2961 if (node_parent == NULL)
2964 ti_new = newTreeInfo();
2965 setTreeInfoToDefaults(ti_new, node_parent->type);
2967 ti_new->node_parent = node_parent;
2968 ti_new->parent_link = TRUE;
2970 setString(&ti_new->identifier, node_parent->identifier);
2971 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2972 setString(&ti_new->name_sorting, ti_new->name);
2974 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2975 setString(&ti_new->fullpath, node_parent->fullpath);
2977 ti_new->sort_priority = LEVELCLASS_PARENT;
2978 ti_new->latest_engine = node_parent->latest_engine;
2980 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2982 pushTreeInfo(&node_parent->node_group, ti_new);
2987 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2989 if (node_first == NULL)
2992 TreeInfo *ti_new = newTreeInfo();
2993 int type = node_first->type;
2995 setTreeInfoToDefaults(ti_new, type);
2997 ti_new->node_parent = NULL;
2998 ti_new->parent_link = FALSE;
3000 setString(&ti_new->identifier, "top_tree_node");
3001 setString(&ti_new->name, TREE_INFOTEXT(type));
3002 setString(&ti_new->name_sorting, ti_new->name);
3004 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3005 setString(&ti_new->fullpath, ".");
3007 ti_new->sort_priority = LEVELCLASS_TOP;
3008 ti_new->latest_engine = node_first->latest_engine;
3010 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3012 ti_new->node_group = node_first;
3013 ti_new->level_group = TRUE;
3015 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3017 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3018 setString(&ti_new2->name_sorting, ti_new2->name);
3023 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3027 if (node->node_group)
3028 setTreeInfoParentNodes(node->node_group, node);
3030 node->node_parent = node_parent;
3037 // ----------------------------------------------------------------------------
3038 // functions for handling level and custom artwork info cache
3039 // ----------------------------------------------------------------------------
3041 static void LoadArtworkInfoCache(void)
3043 InitCacheDirectory();
3045 if (artworkinfo_cache_old == NULL)
3047 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3049 // try to load artwork info hash from already existing cache file
3050 artworkinfo_cache_old = loadSetupFileHash(filename);
3052 // try to get program version that artwork info cache was written with
3053 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3055 // check program version of artwork info cache against current version
3056 if (!strEqual(version, program.version_string))
3058 freeSetupFileHash(artworkinfo_cache_old);
3060 artworkinfo_cache_old = NULL;
3063 // if no artwork info cache file was found, start with empty hash
3064 if (artworkinfo_cache_old == NULL)
3065 artworkinfo_cache_old = newSetupFileHash();
3070 if (artworkinfo_cache_new == NULL)
3071 artworkinfo_cache_new = newSetupFileHash();
3073 update_artworkinfo_cache = FALSE;
3076 static void SaveArtworkInfoCache(void)
3078 if (!update_artworkinfo_cache)
3081 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3083 InitCacheDirectory();
3085 saveSetupFileHash(artworkinfo_cache_new, filename);
3090 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3092 static char *prefix = NULL;
3094 checked_free(prefix);
3096 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3101 // (identical to above function, but separate string buffer needed -- nasty)
3102 static char *getCacheToken(char *prefix, char *suffix)
3104 static char *token = NULL;
3106 checked_free(token);
3108 token = getStringCat2WithSeparator(prefix, suffix, ".");
3113 static char *getFileTimestampString(char *filename)
3115 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3118 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3120 struct stat file_status;
3122 if (timestamp_string == NULL)
3125 if (!fileExists(filename)) // file does not exist
3126 return (atoi(timestamp_string) != 0);
3128 if (stat(filename, &file_status) != 0) // cannot stat file
3131 return (file_status.st_mtime != atoi(timestamp_string));
3134 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3136 char *identifier = level_node->subdir;
3137 char *type_string = ARTWORK_DIRECTORY(type);
3138 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3139 char *token_main = getCacheToken(token_prefix, "CACHED");
3140 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3141 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3142 TreeInfo *artwork_info = NULL;
3144 if (!use_artworkinfo_cache)
3147 if (optional_tokens_hash == NULL)
3151 // create hash from list of optional tokens (for quick access)
3152 optional_tokens_hash = newSetupFileHash();
3153 for (i = 0; optional_tokens[i] != NULL; i++)
3154 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3161 artwork_info = newTreeInfo();
3162 setTreeInfoToDefaults(artwork_info, type);
3164 // set all structure fields according to the token/value pairs
3165 ldi = *artwork_info;
3166 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3168 char *token_suffix = artworkinfo_tokens[i].text;
3169 char *token = getCacheToken(token_prefix, token_suffix);
3170 char *value = getHashEntry(artworkinfo_cache_old, token);
3172 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3174 setSetupInfo(artworkinfo_tokens, i, value);
3176 // check if cache entry for this item is mandatory, but missing
3177 if (value == NULL && !optional)
3179 Warn("missing cache entry '%s'", token);
3185 *artwork_info = ldi;
3190 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3191 LEVELINFO_FILENAME);
3192 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3193 ARTWORKINFO_FILENAME(type));
3195 // check if corresponding "levelinfo.conf" file has changed
3196 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3197 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3199 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3202 // check if corresponding "<artworkinfo>.conf" file has changed
3203 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3204 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3206 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3209 checked_free(filename_levelinfo);
3210 checked_free(filename_artworkinfo);
3213 if (!cached && artwork_info != NULL)
3215 freeTreeInfo(artwork_info);
3220 return artwork_info;
3223 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3224 LevelDirTree *level_node, int type)
3226 char *identifier = level_node->subdir;
3227 char *type_string = ARTWORK_DIRECTORY(type);
3228 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3229 char *token_main = getCacheToken(token_prefix, "CACHED");
3230 boolean set_cache_timestamps = TRUE;
3233 setHashEntry(artworkinfo_cache_new, token_main, "true");
3235 if (set_cache_timestamps)
3237 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3238 LEVELINFO_FILENAME);
3239 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3240 ARTWORKINFO_FILENAME(type));
3241 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3242 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3244 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3245 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3247 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3248 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3250 checked_free(filename_levelinfo);
3251 checked_free(filename_artworkinfo);
3252 checked_free(timestamp_levelinfo);
3253 checked_free(timestamp_artworkinfo);
3256 ldi = *artwork_info;
3257 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3259 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3260 char *value = getSetupValue(artworkinfo_tokens[i].type,
3261 artworkinfo_tokens[i].value);
3263 setHashEntry(artworkinfo_cache_new, token, value);
3268 // ----------------------------------------------------------------------------
3269 // functions for loading level info and custom artwork info
3270 // ----------------------------------------------------------------------------
3272 int GetZipFileTreeType(char *zip_filename)
3274 static char *top_dir_path = NULL;
3275 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3276 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3278 GRAPHICSINFO_FILENAME,
3279 SOUNDSINFO_FILENAME,
3285 checked_free(top_dir_path);
3286 top_dir_path = NULL;
3288 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3290 checked_free(top_dir_conf_filename[j]);
3291 top_dir_conf_filename[j] = NULL;
3294 char **zip_entries = zip_list(zip_filename);
3296 // check if zip file successfully opened
3297 if (zip_entries == NULL || zip_entries[0] == NULL)
3298 return TREE_TYPE_UNDEFINED;
3300 // first zip file entry is expected to be top level directory
3301 char *top_dir = zip_entries[0];
3303 // check if valid top level directory found in zip file
3304 if (!strSuffix(top_dir, "/"))
3305 return TREE_TYPE_UNDEFINED;
3307 // get filenames of valid configuration files in top level directory
3308 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3309 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3311 int tree_type = TREE_TYPE_UNDEFINED;
3314 while (zip_entries[e] != NULL)
3316 // check if every zip file entry is below top level directory
3317 if (!strPrefix(zip_entries[e], top_dir))
3318 return TREE_TYPE_UNDEFINED;
3320 // check if this zip file entry is a valid configuration filename
3321 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3323 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3325 // only exactly one valid configuration file allowed
3326 if (tree_type != TREE_TYPE_UNDEFINED)
3327 return TREE_TYPE_UNDEFINED;
3339 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3342 static char *top_dir_path = NULL;
3343 static char *top_dir_conf_filename = NULL;
3345 checked_free(top_dir_path);
3346 checked_free(top_dir_conf_filename);
3348 top_dir_path = NULL;
3349 top_dir_conf_filename = NULL;
3351 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3352 ARTWORKINFO_FILENAME(tree_type));
3354 // check if valid configuration filename determined
3355 if (conf_basename == NULL || strEqual(conf_basename, ""))
3358 char **zip_entries = zip_list(zip_filename);
3360 // check if zip file successfully opened
3361 if (zip_entries == NULL || zip_entries[0] == NULL)
3364 // first zip file entry is expected to be top level directory
3365 char *top_dir = zip_entries[0];
3367 // check if valid top level directory found in zip file
3368 if (!strSuffix(top_dir, "/"))
3371 // get path of extracted top level directory
3372 top_dir_path = getPath2(directory, top_dir);
3374 // remove trailing directory separator from top level directory path
3375 // (required to be able to check for file and directory in next step)
3376 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3378 // check if zip file's top level directory already exists in target directory
3379 if (fileExists(top_dir_path)) // (checks for file and directory)
3382 // get filename of configuration file in top level directory
3383 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3385 boolean found_top_dir_conf_filename = FALSE;
3388 while (zip_entries[i] != NULL)
3390 // check if every zip file entry is below top level directory
3391 if (!strPrefix(zip_entries[i], top_dir))
3394 // check if this zip file entry is the configuration filename
3395 if (strEqual(zip_entries[i], top_dir_conf_filename))
3396 found_top_dir_conf_filename = TRUE;
3401 // check if valid configuration filename was found in zip file
3402 if (!found_top_dir_conf_filename)
3408 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3411 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3414 if (!zip_file_valid)
3416 Warn("zip file '%s' rejected!", zip_filename);
3421 char **zip_entries = zip_extract(zip_filename, directory);
3423 if (zip_entries == NULL)
3425 Warn("zip file '%s' could not be extracted!", zip_filename);
3430 Info("zip file '%s' successfully extracted!", zip_filename);
3432 // first zip file entry contains top level directory
3433 char *top_dir = zip_entries[0];
3435 // remove trailing directory separator from top level directory
3436 top_dir[strlen(top_dir) - 1] = '\0';
3441 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3444 DirectoryEntry *dir_entry;
3446 if ((dir = openDirectory(directory)) == NULL)
3448 // display error if directory is main "options.graphics_directory" etc.
3449 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3450 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3451 Warn("cannot read directory '%s'", directory);
3456 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3458 // skip non-zip files (and also directories with zip extension)
3459 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3462 char *zip_filename = getPath2(directory, dir_entry->basename);
3463 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3464 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3466 // check if zip file hasn't already been extracted or rejected
3467 if (!fileExists(zip_filename_extracted) &&
3468 !fileExists(zip_filename_rejected))
3470 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3472 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3473 zip_filename_rejected);
3476 // create empty file to mark zip file as extracted or rejected
3477 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3478 fclose(marker_file);
3481 free(zip_filename_extracted);
3482 free(zip_filename_rejected);
3486 closeDirectory(dir);
3489 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3490 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3492 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3493 TreeInfo *node_parent,
3494 char *level_directory,
3495 char *directory_name)
3497 char *directory_path = getPath2(level_directory, directory_name);
3498 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3499 SetupFileHash *setup_file_hash;
3500 LevelDirTree *leveldir_new = NULL;
3503 // unless debugging, silently ignore directories without "levelinfo.conf"
3504 if (!options.debug && !fileExists(filename))
3506 free(directory_path);
3512 setup_file_hash = loadSetupFileHash(filename);
3514 if (setup_file_hash == NULL)
3516 #if DEBUG_NO_CONFIG_FILE
3517 Debug("setup", "ignoring level directory '%s'", directory_path);
3520 free(directory_path);
3526 leveldir_new = newTreeInfo();
3529 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3531 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3533 leveldir_new->subdir = getStringCopy(directory_name);
3535 // set all structure fields according to the token/value pairs
3536 ldi = *leveldir_new;
3537 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3538 setSetupInfo(levelinfo_tokens, i,
3539 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3540 *leveldir_new = ldi;
3542 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3543 setString(&leveldir_new->name, leveldir_new->subdir);
3545 if (leveldir_new->identifier == NULL)
3546 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3548 if (leveldir_new->name_sorting == NULL)
3549 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3551 if (node_parent == NULL) // top level group
3553 leveldir_new->basepath = getStringCopy(level_directory);
3554 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3556 else // sub level group
3558 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3559 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3562 leveldir_new->last_level =
3563 leveldir_new->first_level + leveldir_new->levels - 1;
3565 leveldir_new->in_user_dir =
3566 (!strEqual(leveldir_new->basepath, options.level_directory));
3568 // adjust some settings if user's private level directory was detected
3569 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3570 leveldir_new->in_user_dir &&
3571 (strEqual(leveldir_new->subdir, getLoginName()) ||
3572 strEqual(leveldir_new->name, getLoginName()) ||
3573 strEqual(leveldir_new->author, getRealName())))
3575 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3576 leveldir_new->readonly = FALSE;
3579 leveldir_new->user_defined =
3580 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3582 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3584 leveldir_new->handicap_level = // set handicap to default value
3585 (leveldir_new->user_defined || !leveldir_new->handicap ?
3586 leveldir_new->last_level : leveldir_new->first_level);
3588 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3590 pushTreeInfo(node_first, leveldir_new);
3592 freeSetupFileHash(setup_file_hash);
3594 if (leveldir_new->level_group)
3596 // create node to link back to current level directory
3597 createParentTreeInfoNode(leveldir_new);
3599 // recursively step into sub-directory and look for more level series
3600 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3601 leveldir_new, directory_path);
3604 free(directory_path);
3610 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3611 TreeInfo *node_parent,
3612 char *level_directory)
3614 // ---------- 1st stage: process any level set zip files ----------
3616 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3618 // ---------- 2nd stage: check for level set directories ----------
3621 DirectoryEntry *dir_entry;
3622 boolean valid_entry_found = FALSE;
3624 if ((dir = openDirectory(level_directory)) == NULL)
3626 Warn("cannot read level directory '%s'", level_directory);
3631 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3633 char *directory_name = dir_entry->basename;
3634 char *directory_path = getPath2(level_directory, directory_name);
3636 // skip entries for current and parent directory
3637 if (strEqual(directory_name, ".") ||
3638 strEqual(directory_name, ".."))
3640 free(directory_path);
3645 // find out if directory entry is itself a directory
3646 if (!dir_entry->is_directory) // not a directory
3648 free(directory_path);
3653 free(directory_path);
3655 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3656 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3657 strEqual(directory_name, MUSIC_DIRECTORY))
3660 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3665 closeDirectory(dir);
3667 // special case: top level directory may directly contain "levelinfo.conf"
3668 if (node_parent == NULL && !valid_entry_found)
3670 // check if this directory directly contains a file "levelinfo.conf"
3671 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3672 level_directory, ".");
3675 if (!valid_entry_found)
3676 Warn("cannot find any valid level series in directory '%s'",
3680 boolean AdjustGraphicsForEMC(void)
3682 boolean settings_changed = FALSE;
3684 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3685 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3687 return settings_changed;
3690 boolean AdjustSoundsForEMC(void)
3692 boolean settings_changed = FALSE;
3694 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3695 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3697 return settings_changed;
3700 void LoadLevelInfo(void)
3702 InitUserLevelDirectory(getLoginName());
3704 DrawInitText("Loading level series", 120, FC_GREEN);
3706 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3707 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3709 leveldir_first = createTopTreeInfoNode(leveldir_first);
3711 /* after loading all level set information, clone the level directory tree
3712 and remove all level sets without levels (these may still contain artwork
3713 to be offered in the setup menu as "custom artwork", and are therefore
3714 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3715 leveldir_first_all = leveldir_first;
3716 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3718 AdjustGraphicsForEMC();
3719 AdjustSoundsForEMC();
3721 // before sorting, the first entries will be from the user directory
3722 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3724 if (leveldir_first == NULL)
3725 Fail("cannot find any valid level series in any directory");
3727 sortTreeInfo(&leveldir_first);
3729 #if ENABLE_UNUSED_CODE
3730 dumpTreeInfo(leveldir_first, 0);
3734 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3735 TreeInfo *node_parent,
3736 char *base_directory,
3737 char *directory_name, int type)
3739 char *directory_path = getPath2(base_directory, directory_name);
3740 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3741 SetupFileHash *setup_file_hash = NULL;
3742 TreeInfo *artwork_new = NULL;
3745 if (fileExists(filename))
3746 setup_file_hash = loadSetupFileHash(filename);
3748 if (setup_file_hash == NULL) // no config file -- look for artwork files
3751 DirectoryEntry *dir_entry;
3752 boolean valid_file_found = FALSE;
3754 if ((dir = openDirectory(directory_path)) != NULL)
3756 while ((dir_entry = readDirectory(dir)) != NULL)
3758 if (FileIsArtworkType(dir_entry->filename, type))
3760 valid_file_found = TRUE;
3766 closeDirectory(dir);
3769 if (!valid_file_found)
3771 #if DEBUG_NO_CONFIG_FILE
3772 if (!strEqual(directory_name, "."))
3773 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3776 free(directory_path);
3783 artwork_new = newTreeInfo();
3786 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3788 setTreeInfoToDefaults(artwork_new, type);
3790 artwork_new->subdir = getStringCopy(directory_name);
3792 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3794 // set all structure fields according to the token/value pairs
3796 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3797 setSetupInfo(levelinfo_tokens, i,
3798 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3801 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3802 setString(&artwork_new->name, artwork_new->subdir);
3804 if (artwork_new->identifier == NULL)
3805 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3807 if (artwork_new->name_sorting == NULL)
3808 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3811 if (node_parent == NULL) // top level group
3813 artwork_new->basepath = getStringCopy(base_directory);
3814 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3816 else // sub level group
3818 artwork_new->basepath = getStringCopy(node_parent->basepath);
3819 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3822 artwork_new->in_user_dir =
3823 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3825 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3827 if (setup_file_hash == NULL) // (after determining ".user_defined")
3829 if (strEqual(artwork_new->subdir, "."))
3831 if (artwork_new->user_defined)
3833 setString(&artwork_new->identifier, "private");
3834 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3838 setString(&artwork_new->identifier, "classic");
3839 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3842 setString(&artwork_new->class_desc,
3843 getLevelClassDescription(artwork_new));
3847 setString(&artwork_new->identifier, artwork_new->subdir);
3850 setString(&artwork_new->name, artwork_new->identifier);
3851 setString(&artwork_new->name_sorting, artwork_new->name);
3854 pushTreeInfo(node_first, artwork_new);
3856 freeSetupFileHash(setup_file_hash);
3858 free(directory_path);
3864 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3865 TreeInfo *node_parent,
3866 char *base_directory, int type)
3868 // ---------- 1st stage: process any artwork set zip files ----------
3870 ProcessZipFilesInDirectory(base_directory, type);
3872 // ---------- 2nd stage: check for artwork set directories ----------
3875 DirectoryEntry *dir_entry;
3876 boolean valid_entry_found = FALSE;
3878 if ((dir = openDirectory(base_directory)) == NULL)
3880 // display error if directory is main "options.graphics_directory" etc.
3881 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3882 Warn("cannot read directory '%s'", base_directory);
3887 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3889 char *directory_name = dir_entry->basename;
3890 char *directory_path = getPath2(base_directory, directory_name);
3892 // skip directory entries for current and parent directory
3893 if (strEqual(directory_name, ".") ||
3894 strEqual(directory_name, ".."))
3896 free(directory_path);
3901 // skip directory entries which are not a directory
3902 if (!dir_entry->is_directory) // not a directory
3904 free(directory_path);
3909 free(directory_path);
3911 // check if this directory contains artwork with or without config file
3912 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3914 directory_name, type);
3917 closeDirectory(dir);
3919 // check if this directory directly contains artwork itself
3920 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3921 base_directory, ".",
3923 if (!valid_entry_found)
3924 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3927 static TreeInfo *getDummyArtworkInfo(int type)
3929 // this is only needed when there is completely no artwork available
3930 TreeInfo *artwork_new = newTreeInfo();
3932 setTreeInfoToDefaults(artwork_new, type);
3934 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3935 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3936 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3938 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3939 setString(&artwork_new->name, UNDEFINED_FILENAME);
3940 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3945 void SetCurrentArtwork(int type)
3947 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3948 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3949 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3950 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3952 // set current artwork to artwork configured in setup menu
3953 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3955 // if not found, set current artwork to default artwork
3956 if (*current_ptr == NULL)
3957 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3959 // if not found, set current artwork to first artwork in tree
3960 if (*current_ptr == NULL)
3961 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3964 void ChangeCurrentArtworkIfNeeded(int type)
3966 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3967 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3969 if (!strEqual(current_identifier, setup_set))
3970 SetCurrentArtwork(type);
3973 void LoadArtworkInfo(void)
3975 LoadArtworkInfoCache();
3977 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3979 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3980 options.graphics_directory,
3981 TREE_TYPE_GRAPHICS_DIR);
3982 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3983 getUserGraphicsDir(),
3984 TREE_TYPE_GRAPHICS_DIR);
3986 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3987 options.sounds_directory,
3988 TREE_TYPE_SOUNDS_DIR);
3989 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3991 TREE_TYPE_SOUNDS_DIR);
3993 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3994 options.music_directory,
3995 TREE_TYPE_MUSIC_DIR);
3996 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3998 TREE_TYPE_MUSIC_DIR);
4000 if (artwork.gfx_first == NULL)
4001 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4002 if (artwork.snd_first == NULL)
4003 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4004 if (artwork.mus_first == NULL)
4005 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4007 // before sorting, the first entries will be from the user directory
4008 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4009 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4010 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4012 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4013 artwork.snd_current_identifier = artwork.snd_current->identifier;
4014 artwork.mus_current_identifier = artwork.mus_current->identifier;
4016 #if ENABLE_UNUSED_CODE
4017 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4018 artwork.gfx_current_identifier);
4019 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4020 artwork.snd_current_identifier);
4021 Debug("setup:LoadArtworkInfo", "music set == %s",
4022 artwork.mus_current_identifier);
4025 sortTreeInfo(&artwork.gfx_first);
4026 sortTreeInfo(&artwork.snd_first);
4027 sortTreeInfo(&artwork.mus_first);
4029 #if ENABLE_UNUSED_CODE
4030 dumpTreeInfo(artwork.gfx_first, 0);
4031 dumpTreeInfo(artwork.snd_first, 0);
4032 dumpTreeInfo(artwork.mus_first, 0);
4036 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4038 ArtworkDirTree *artwork_new = newTreeInfo();
4039 char *top_node_name = "standalone artwork";
4041 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4043 artwork_new->level_group = TRUE;
4045 setString(&artwork_new->identifier, top_node_name);
4046 setString(&artwork_new->name, top_node_name);
4047 setString(&artwork_new->name_sorting, top_node_name);
4049 // create node to link back to current custom artwork directory
4050 createParentTreeInfoNode(artwork_new);
4052 // move existing custom artwork tree into newly created sub-tree
4053 artwork_new->node_group->next = *artwork_node;
4055 // change custom artwork tree to contain only newly created node
4056 *artwork_node = artwork_new;
4059 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4060 ArtworkDirTree *node_parent,
4061 LevelDirTree *level_node,
4062 boolean empty_level_set_mode)
4064 int type = (*artwork_node)->type;
4066 // recursively check all level directories for artwork sub-directories
4070 boolean empty_level_set = (level_node->levels == 0);
4072 // check all tree entries for artwork, but skip parent link entries
4073 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4075 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4076 boolean cached = (artwork_new != NULL);
4080 pushTreeInfo(artwork_node, artwork_new);
4084 TreeInfo *topnode_last = *artwork_node;
4085 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4086 ARTWORK_DIRECTORY(type));
4088 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4090 if (topnode_last != *artwork_node) // check for newly added node
4092 artwork_new = *artwork_node;
4094 setString(&artwork_new->identifier, level_node->subdir);
4095 setString(&artwork_new->name, level_node->name);
4096 setString(&artwork_new->name_sorting, level_node->name_sorting);
4098 artwork_new->sort_priority = level_node->sort_priority;
4099 artwork_new->in_user_dir = level_node->in_user_dir;
4101 update_artworkinfo_cache = TRUE;
4107 // insert artwork info (from old cache or filesystem) into new cache
4108 if (artwork_new != NULL)
4109 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4112 DrawInitText(level_node->name, 150, FC_YELLOW);
4114 if (level_node->node_group != NULL)
4116 TreeInfo *artwork_new = newTreeInfo();
4119 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4121 setTreeInfoToDefaults(artwork_new, type);
4123 artwork_new->level_group = TRUE;
4125 setString(&artwork_new->identifier, level_node->subdir);
4127 if (node_parent == NULL) // check for top tree node
4129 char *top_node_name = (empty_level_set_mode ?
4130 "artwork for certain level sets" :
4131 "artwork included in level sets");
4133 setString(&artwork_new->name, top_node_name);
4134 setString(&artwork_new->name_sorting, top_node_name);
4138 setString(&artwork_new->name, level_node->name);
4139 setString(&artwork_new->name_sorting, level_node->name_sorting);
4142 pushTreeInfo(artwork_node, artwork_new);
4144 // create node to link back to current custom artwork directory
4145 createParentTreeInfoNode(artwork_new);
4147 // recursively step into sub-directory and look for more custom artwork
4148 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4149 level_node->node_group,
4150 empty_level_set_mode);
4152 // if sub-tree has no custom artwork at all, remove it
4153 if (artwork_new->node_group->next == NULL)
4154 removeTreeInfo(artwork_node);
4157 level_node = level_node->next;
4161 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4163 // move peviously loaded artwork tree into separate sub-tree
4164 MoveArtworkInfoIntoSubTree(artwork_node);
4166 // load artwork from level sets into separate sub-trees
4167 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4168 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4170 // add top tree node over all three separate sub-trees
4171 *artwork_node = createTopTreeInfoNode(*artwork_node);
4173 // set all parent links (back links) in complete artwork tree
4174 setTreeInfoParentNodes(*artwork_node, NULL);
4177 void LoadLevelArtworkInfo(void)
4179 print_timestamp_init("LoadLevelArtworkInfo");
4181 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4183 print_timestamp_time("DrawTimeText");
4185 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4186 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4187 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4188 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4189 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4190 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4192 SaveArtworkInfoCache();
4194 print_timestamp_time("SaveArtworkInfoCache");
4196 // needed for reloading level artwork not known at ealier stage
4197 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4198 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4199 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4201 print_timestamp_time("getTreeInfoFromIdentifier");
4203 sortTreeInfo(&artwork.gfx_first);
4204 sortTreeInfo(&artwork.snd_first);
4205 sortTreeInfo(&artwork.mus_first);
4207 print_timestamp_time("sortTreeInfo");
4209 #if ENABLE_UNUSED_CODE
4210 dumpTreeInfo(artwork.gfx_first, 0);
4211 dumpTreeInfo(artwork.snd_first, 0);
4212 dumpTreeInfo(artwork.mus_first, 0);
4215 print_timestamp_done("LoadLevelArtworkInfo");
4218 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4219 char *tree_subdir_new, int type)
4221 if (tree_node_old == NULL)
4223 if (type == TREE_TYPE_LEVEL_DIR)
4225 // get level info tree node of personal user level set
4226 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4228 // this may happen if "setup.internal.create_user_levelset" is FALSE
4229 // or if file "levelinfo.conf" is missing in personal user level set
4230 if (tree_node_old == NULL)
4231 tree_node_old = leveldir_first->node_group;
4235 // get artwork info tree node of first artwork set
4236 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4240 if (tree_dir == NULL)
4241 tree_dir = TREE_USERDIR(type);
4243 if (tree_node_old == NULL ||
4245 tree_subdir_new == NULL) // should not happen
4248 int draw_deactivation_mask = GetDrawDeactivationMask();
4250 // override draw deactivation mask (temporarily disable drawing)
4251 SetDrawDeactivationMask(REDRAW_ALL);
4253 if (type == TREE_TYPE_LEVEL_DIR)
4255 // load new level set config and add it next to first user level set
4256 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4257 tree_node_old->node_parent,
4258 tree_dir, tree_subdir_new);
4262 // load new artwork set config and add it next to first artwork set
4263 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4264 tree_node_old->node_parent,
4265 tree_dir, tree_subdir_new, type);
4268 // set draw deactivation mask to previous value
4269 SetDrawDeactivationMask(draw_deactivation_mask);
4271 // get first node of level or artwork info tree
4272 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4274 // get tree info node of newly added level or artwork set
4275 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4278 if (tree_node_new == NULL) // should not happen
4281 // correct top link and parent node link of newly created tree node
4282 tree_node_new->node_top = tree_node_old->node_top;
4283 tree_node_new->node_parent = tree_node_old->node_parent;
4285 // sort tree info to adjust position of newly added tree set
4286 sortTreeInfo(tree_node_first);
4291 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4292 char *tree_subdir_new, int type)
4294 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4295 Fail("internal tree info structure corrupted -- aborting");
4298 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4300 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4303 char *getArtworkIdentifierForUserLevelSet(int type)
4305 char *classic_artwork_set = getClassicArtworkSet(type);
4307 // check for custom artwork configured in "levelinfo.conf"
4308 char *leveldir_artwork_set =
4309 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4310 boolean has_leveldir_artwork_set =
4311 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4312 classic_artwork_set));
4314 // check for custom artwork in sub-directory "graphics" etc.
4315 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4316 char *leveldir_identifier = leveldir_current->identifier;
4317 boolean has_artwork_subdir =
4318 (getTreeInfoFromIdentifier(artwork_first_node,
4319 leveldir_identifier) != NULL);
4321 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4322 has_artwork_subdir ? leveldir_identifier :
4323 classic_artwork_set);
4326 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4328 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4329 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4330 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4334 ti = getTreeInfoFromIdentifier(artwork_first_node,
4335 ARTWORK_DEFAULT_SUBDIR(type));
4337 Fail("cannot find default graphics -- should not happen");
4343 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4345 char *graphics_set =
4346 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4348 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4350 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4352 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4353 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4354 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4357 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4358 char *level_author, int num_levels)
4360 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4361 char *filename_tmp = getStringCat2(filename, ".tmp");
4363 FILE *file_tmp = NULL;
4364 char line[MAX_LINE_LEN];
4365 boolean success = FALSE;
4366 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4368 // update values in level directory tree
4370 if (level_name != NULL)
4371 setString(&leveldir->name, level_name);
4373 if (level_author != NULL)
4374 setString(&leveldir->author, level_author);
4376 if (num_levels != -1)
4377 leveldir->levels = num_levels;
4379 // update values that depend on other values
4381 setString(&leveldir->name_sorting, leveldir->name);
4383 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4385 // sort order of level sets may have changed
4386 sortTreeInfo(&leveldir_first);
4388 if ((file = fopen(filename, MODE_READ)) &&
4389 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4391 while (fgets(line, MAX_LINE_LEN, file))
4393 if (strPrefix(line, "name:") && level_name != NULL)
4394 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4395 else if (strPrefix(line, "author:") && level_author != NULL)
4396 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4397 else if (strPrefix(line, "levels:") && num_levels != -1)
4398 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4400 fputs(line, file_tmp);
4413 success = (rename(filename_tmp, filename) == 0);
4421 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4422 char *level_author, int num_levels,
4423 boolean use_artwork_set)
4425 LevelDirTree *level_info;
4430 // create user level sub-directory, if needed
4431 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4433 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4435 if (!(file = fopen(filename, MODE_WRITE)))
4437 Warn("cannot write level info file '%s'", filename);
4444 level_info = newTreeInfo();
4446 // always start with reliable default values
4447 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4449 setString(&level_info->name, level_name);
4450 setString(&level_info->author, level_author);
4451 level_info->levels = num_levels;
4452 level_info->first_level = 1;
4453 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4454 level_info->readonly = FALSE;
4456 if (use_artwork_set)
4458 level_info->graphics_set =
4459 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4460 level_info->sounds_set =
4461 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4462 level_info->music_set =
4463 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4466 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4468 fprintFileHeader(file, LEVELINFO_FILENAME);
4471 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4473 if (i == LEVELINFO_TOKEN_NAME ||
4474 i == LEVELINFO_TOKEN_AUTHOR ||
4475 i == LEVELINFO_TOKEN_LEVELS ||
4476 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4477 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4478 i == LEVELINFO_TOKEN_READONLY ||
4479 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4480 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4481 i == LEVELINFO_TOKEN_MUSIC_SET)))
4482 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4484 // just to make things nicer :)
4485 if (i == LEVELINFO_TOKEN_AUTHOR ||
4486 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4487 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4488 fprintf(file, "\n");
4491 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4495 SetFilePermissions(filename, PERMS_PRIVATE);
4497 freeTreeInfo(level_info);
4503 static void SaveUserLevelInfo(void)
4505 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4508 char *getSetupValue(int type, void *value)
4510 static char value_string[MAX_LINE_LEN];
4518 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4522 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4526 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4527 *(int *)value == FALSE ? "off" : "on"));
4531 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4534 case TYPE_YES_NO_AUTO:
4535 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4536 *(int *)value == FALSE ? "no" : "yes"));
4540 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4544 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4548 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4552 sprintf(value_string, "%d", *(int *)value);
4556 if (*(char **)value == NULL)
4559 strcpy(value_string, *(char **)value);
4563 sprintf(value_string, "player_%d", *(int *)value + 1);
4567 value_string[0] = '\0';
4571 if (type & TYPE_GHOSTED)
4572 strcpy(value_string, "n/a");
4574 return value_string;
4577 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4581 static char token_string[MAX_LINE_LEN];
4582 int token_type = token_info[token_nr].type;
4583 void *setup_value = token_info[token_nr].value;
4584 char *token_text = token_info[token_nr].text;
4585 char *value_string = getSetupValue(token_type, setup_value);
4587 // build complete token string
4588 sprintf(token_string, "%s%s", prefix, token_text);
4590 // build setup entry line
4591 line = getFormattedSetupEntry(token_string, value_string);
4593 if (token_type == TYPE_KEY_X11)
4595 Key key = *(Key *)setup_value;
4596 char *keyname = getKeyNameFromKey(key);
4598 // add comment, if useful
4599 if (!strEqual(keyname, "(undefined)") &&
4600 !strEqual(keyname, "(unknown)"))
4602 // add at least one whitespace
4604 for (i = strlen(line); i < token_comment_position; i++)
4608 strcat(line, keyname);
4615 static void InitLastPlayedLevels_ParentNode(void)
4617 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4618 LevelDirTree *leveldir_new = NULL;
4620 // check if parent node for last played levels already exists
4621 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4624 leveldir_new = newTreeInfo();
4626 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4628 leveldir_new->level_group = TRUE;
4629 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4631 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4632 setString(&leveldir_new->name, "<< (last played level sets)");
4633 setString(&leveldir_new->name_sorting, leveldir_new->name);
4635 pushTreeInfo(leveldir_top, leveldir_new);
4637 // create node to link back to current level directory
4638 createParentTreeInfoNode(leveldir_new);
4641 void UpdateLastPlayedLevels_TreeInfo(void)
4643 char **last_level_series = setup.level_setup.last_level_series;
4644 LevelDirTree *leveldir_last;
4645 TreeInfo **node_new = NULL;
4648 if (last_level_series[0] == NULL)
4651 InitLastPlayedLevels_ParentNode();
4653 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4654 TOKEN_STR_LAST_LEVEL_SERIES,
4655 TREE_NODE_TYPE_GROUP);
4656 if (leveldir_last == NULL)
4659 node_new = &leveldir_last->node_group->next;
4661 freeTreeInfo(*node_new);
4665 for (i = 0; last_level_series[i] != NULL; i++)
4667 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4668 last_level_series[i]);
4669 if (node_last == NULL)
4672 *node_new = getTreeInfoCopy(node_last); // copy complete node
4674 (*node_new)->node_top = &leveldir_first; // correct top node link
4675 (*node_new)->node_parent = leveldir_last; // correct parent node link
4677 (*node_new)->is_copy = TRUE; // mark entry as node copy
4679 (*node_new)->node_group = NULL;
4680 (*node_new)->next = NULL;
4682 (*node_new)->cl_first = -1; // force setting tree cursor
4684 node_new = &((*node_new)->next);
4688 static void UpdateLastPlayedLevels_List(void)
4690 char **last_level_series = setup.level_setup.last_level_series;
4691 int pos = MAX_LEVELDIR_HISTORY - 1;
4694 // search for potentially already existing entry in list of level sets
4695 for (i = 0; last_level_series[i] != NULL; i++)
4696 if (strEqual(last_level_series[i], leveldir_current->identifier))
4699 // move list of level sets one entry down (using potentially free entry)
4700 for (i = pos; i > 0; i--)
4701 setString(&last_level_series[i], last_level_series[i - 1]);
4703 // put last played level set at top position
4704 setString(&last_level_series[0], leveldir_current->identifier);
4707 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4709 static char *identifier = NULL;
4713 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4715 return NULL; // not used
4719 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4721 TREE_NODE_TYPE_COPY);
4722 return (node_new != NULL ? node_new : node);
4726 void StoreLastPlayedLevels(TreeInfo *node)
4728 StoreOrRestoreLastPlayedLevels(node, TRUE);
4731 void RestoreLastPlayedLevels(TreeInfo **node)
4733 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4736 void LoadLevelSetup_LastSeries(void)
4738 // --------------------------------------------------------------------------
4739 // ~/.<program>/levelsetup.conf
4740 // --------------------------------------------------------------------------
4742 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4743 SetupFileHash *level_setup_hash = NULL;
4747 // always start with reliable default values
4748 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4750 // start with empty history of last played level sets
4751 setString(&setup.level_setup.last_level_series[0], NULL);
4753 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4755 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4757 if (leveldir_current == NULL)
4758 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4761 if ((level_setup_hash = loadSetupFileHash(filename)))
4763 char *last_level_series =
4764 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4766 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4768 if (leveldir_current == NULL)
4769 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4771 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4773 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4774 LevelDirTree *leveldir_last;
4776 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4778 last_level_series = getHashEntry(level_setup_hash, token);
4780 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4782 if (leveldir_last != NULL)
4783 setString(&setup.level_setup.last_level_series[pos++],
4787 setString(&setup.level_setup.last_level_series[pos], NULL);
4789 freeSetupFileHash(level_setup_hash);
4793 Debug("setup", "using default setup values");
4799 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4801 // --------------------------------------------------------------------------
4802 // ~/.<program>/levelsetup.conf
4803 // --------------------------------------------------------------------------
4805 // check if the current level directory structure is available at this point
4806 if (leveldir_current == NULL)
4809 char **last_level_series = setup.level_setup.last_level_series;
4810 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4814 InitUserDataDirectory();
4816 UpdateLastPlayedLevels_List();
4818 if (!(file = fopen(filename, MODE_WRITE)))
4820 Warn("cannot write setup file '%s'", filename);
4827 fprintFileHeader(file, LEVELSETUP_FILENAME);
4829 if (deactivate_last_level_series)
4830 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4832 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4833 leveldir_current->identifier));
4835 for (i = 0; last_level_series[i] != NULL; i++)
4837 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4839 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4841 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4846 SetFilePermissions(filename, PERMS_PRIVATE);
4851 void SaveLevelSetup_LastSeries(void)
4853 SaveLevelSetup_LastSeries_Ext(FALSE);
4856 void SaveLevelSetup_LastSeries_Deactivate(void)
4858 SaveLevelSetup_LastSeries_Ext(TRUE);
4861 static void checkSeriesInfo(void)
4863 static char *level_directory = NULL;
4866 DirectoryEntry *dir_entry;
4869 checked_free(level_directory);
4871 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4873 level_directory = getPath2((leveldir_current->in_user_dir ?
4874 getUserLevelDir(NULL) :
4875 options.level_directory),
4876 leveldir_current->fullpath);
4878 if ((dir = openDirectory(level_directory)) == NULL)
4880 Warn("cannot read level directory '%s'", level_directory);
4886 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4888 if (strlen(dir_entry->basename) > 4 &&
4889 dir_entry->basename[3] == '.' &&
4890 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4892 char levelnum_str[4];
4895 strncpy(levelnum_str, dir_entry->basename, 3);
4896 levelnum_str[3] = '\0';
4898 levelnum_value = atoi(levelnum_str);
4900 if (levelnum_value < leveldir_current->first_level)
4902 Warn("additional level %d found", levelnum_value);
4904 leveldir_current->first_level = levelnum_value;
4906 else if (levelnum_value > leveldir_current->last_level)
4908 Warn("additional level %d found", levelnum_value);
4910 leveldir_current->last_level = levelnum_value;
4916 closeDirectory(dir);
4919 void LoadLevelSetup_SeriesInfo(void)
4922 SetupFileHash *level_setup_hash = NULL;
4923 char *level_subdir = leveldir_current->subdir;
4926 // always start with reliable default values
4927 level_nr = leveldir_current->first_level;
4929 for (i = 0; i < MAX_LEVELS; i++)
4931 LevelStats_setPlayed(i, 0);
4932 LevelStats_setSolved(i, 0);
4937 // --------------------------------------------------------------------------
4938 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4939 // --------------------------------------------------------------------------
4941 level_subdir = leveldir_current->subdir;
4943 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4945 if ((level_setup_hash = loadSetupFileHash(filename)))
4949 // get last played level in this level set
4951 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4955 level_nr = atoi(token_value);
4957 if (level_nr < leveldir_current->first_level)
4958 level_nr = leveldir_current->first_level;
4959 if (level_nr > leveldir_current->last_level)
4960 level_nr = leveldir_current->last_level;
4963 // get handicap level in this level set
4965 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4969 int level_nr = atoi(token_value);
4971 if (level_nr < leveldir_current->first_level)
4972 level_nr = leveldir_current->first_level;
4973 if (level_nr > leveldir_current->last_level + 1)
4974 level_nr = leveldir_current->last_level;
4976 if (leveldir_current->user_defined || !leveldir_current->handicap)
4977 level_nr = leveldir_current->last_level;
4979 leveldir_current->handicap_level = level_nr;
4982 // get number of played and solved levels in this level set
4984 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4986 char *token = HASH_ITERATION_TOKEN(itr);
4987 char *value = HASH_ITERATION_VALUE(itr);
4989 if (strlen(token) == 3 &&
4990 token[0] >= '0' && token[0] <= '9' &&
4991 token[1] >= '0' && token[1] <= '9' &&
4992 token[2] >= '0' && token[2] <= '9')
4994 int level_nr = atoi(token);
4997 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4999 value = strchr(value, ' ');
5002 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5005 END_HASH_ITERATION(hash, itr)
5007 freeSetupFileHash(level_setup_hash);
5011 Debug("setup", "using default setup values");
5017 void SaveLevelSetup_SeriesInfo(void)
5020 char *level_subdir = leveldir_current->subdir;
5021 char *level_nr_str = int2str(level_nr, 0);
5022 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5026 // --------------------------------------------------------------------------
5027 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5028 // --------------------------------------------------------------------------
5030 InitLevelSetupDirectory(level_subdir);
5032 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5034 if (!(file = fopen(filename, MODE_WRITE)))
5036 Warn("cannot write setup file '%s'", filename);
5043 fprintFileHeader(file, LEVELSETUP_FILENAME);
5045 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5047 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5048 handicap_level_str));
5050 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5053 if (LevelStats_getPlayed(i) > 0 ||
5054 LevelStats_getSolved(i) > 0)
5059 sprintf(token, "%03d", i);
5060 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5062 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5068 SetFilePermissions(filename, PERMS_PRIVATE);
5073 int LevelStats_getPlayed(int nr)
5075 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5078 int LevelStats_getSolved(int nr)
5080 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5083 void LevelStats_setPlayed(int nr, int value)
5085 if (nr >= 0 && nr < MAX_LEVELS)
5086 level_stats[nr].played = value;
5089 void LevelStats_setSolved(int nr, int value)
5091 if (nr >= 0 && nr < MAX_LEVELS)
5092 level_stats[nr].solved = value;
5095 void LevelStats_incPlayed(int nr)
5097 if (nr >= 0 && nr < MAX_LEVELS)
5098 level_stats[nr].played++;
5101 void LevelStats_incSolved(int nr)
5103 if (nr >= 0 && nr < MAX_LEVELS)
5104 level_stats[nr].solved++;
5107 void LoadUserSetup(void)
5109 // --------------------------------------------------------------------------
5110 // ~/.<program>/usersetup.conf
5111 // --------------------------------------------------------------------------
5113 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5114 SetupFileHash *user_setup_hash = NULL;
5116 // always start with reliable default values
5119 if ((user_setup_hash = loadSetupFileHash(filename)))
5123 // get last selected user number
5124 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5127 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5129 freeSetupFileHash(user_setup_hash);
5133 Debug("setup", "using default setup values");
5139 void SaveUserSetup(void)
5141 // --------------------------------------------------------------------------
5142 // ~/.<program>/usersetup.conf
5143 // --------------------------------------------------------------------------
5145 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5148 InitMainUserDataDirectory();
5150 if (!(file = fopen(filename, MODE_WRITE)))
5152 Warn("cannot write setup file '%s'", filename);
5159 fprintFileHeader(file, USERSETUP_FILENAME);
5161 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5165 SetFilePermissions(filename, PERMS_PRIVATE);