1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // http://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
21 #if !defined(PLATFORM_WIN32)
23 #include <sys/param.h>
31 #include "zip/miniunz.h"
34 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
35 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
37 #define NUM_LEVELCLASS_DESC 8
39 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
52 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
53 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
54 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
59 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
60 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
63 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
64 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
65 IS_LEVELCLASS_BD(n) ? 2 : \
66 IS_LEVELCLASS_EM(n) ? 3 : \
67 IS_LEVELCLASS_SP(n) ? 4 : \
68 IS_LEVELCLASS_DX(n) ? 5 : \
69 IS_LEVELCLASS_SB(n) ? 6 : \
70 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
71 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
74 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
75 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
76 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
77 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
80 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
81 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
82 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
83 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
86 #define TOKEN_VALUE_POSITION_SHORT 32
87 #define TOKEN_VALUE_POSITION_DEFAULT 40
88 #define TOKEN_COMMENT_POSITION_DEFAULT 60
90 #define MAX_COOKIE_LEN 256
93 static void setTreeInfoToDefaults(TreeInfo *, int);
94 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
95 static int compareTreeInfoEntries(const void *, const void *);
97 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
98 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
100 static SetupFileHash *artworkinfo_cache_old = NULL;
101 static SetupFileHash *artworkinfo_cache_new = NULL;
102 static boolean use_artworkinfo_cache = TRUE;
105 // ----------------------------------------------------------------------------
107 // ----------------------------------------------------------------------------
109 static char *getLevelClassDescription(TreeInfo *ti)
111 int position = ti->sort_priority / 100;
113 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
114 return levelclass_desc[position];
116 return "Unknown Level Class";
119 static char *getScoreDir(char *level_subdir)
121 static char *score_dir = NULL;
122 static char *score_level_dir = NULL;
123 char *score_subdir = SCORES_DIRECTORY;
125 if (score_dir == NULL)
127 if (program.global_scores)
128 score_dir = getPath2(getCommonDataDir(), score_subdir);
130 score_dir = getPath2(getUserGameDataDir(), score_subdir);
133 if (level_subdir != NULL)
135 checked_free(score_level_dir);
137 score_level_dir = getPath2(score_dir, level_subdir);
139 return score_level_dir;
145 static char *getLevelSetupDir(char *level_subdir)
147 static char *levelsetup_dir = NULL;
148 char *data_dir = getUserGameDataDir();
149 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
151 checked_free(levelsetup_dir);
153 if (level_subdir != NULL)
154 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
156 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
158 return levelsetup_dir;
161 static char *getCacheDir(void)
163 static char *cache_dir = NULL;
165 if (cache_dir == NULL)
166 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
171 static char *getNetworkDir(void)
173 static char *network_dir = NULL;
175 if (network_dir == NULL)
176 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
181 static char *getLevelDirFromTreeInfo(TreeInfo *node)
183 static char *level_dir = NULL;
186 return options.level_directory;
188 checked_free(level_dir);
190 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
191 options.level_directory), node->fullpath);
196 char *getUserLevelDir(char *level_subdir)
198 static char *userlevel_dir = NULL;
199 char *data_dir = getUserGameDataDir();
200 char *userlevel_subdir = LEVELS_DIRECTORY;
202 checked_free(userlevel_dir);
204 if (level_subdir != NULL)
205 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
207 userlevel_dir = getPath2(data_dir, userlevel_subdir);
209 return userlevel_dir;
212 char *getNetworkLevelDir(char *level_subdir)
214 static char *network_level_dir = NULL;
215 char *data_dir = getNetworkDir();
216 char *networklevel_subdir = LEVELS_DIRECTORY;
218 checked_free(network_level_dir);
220 if (level_subdir != NULL)
221 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
223 network_level_dir = getPath2(data_dir, networklevel_subdir);
225 return network_level_dir;
228 char *getCurrentLevelDir(void)
230 return getLevelDirFromTreeInfo(leveldir_current);
233 char *getNewUserLevelSubdir(void)
235 static char *new_level_subdir = NULL;
236 char *subdir_prefix = getLoginName();
237 char subdir_suffix[10];
238 int max_suffix_number = 1000;
241 while (++i < max_suffix_number)
243 sprintf(subdir_suffix, "_%d", i);
245 checked_free(new_level_subdir);
246 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
248 if (!directoryExists(getUserLevelDir(new_level_subdir)))
252 return new_level_subdir;
255 static char *getTapeDir(char *level_subdir)
257 static char *tape_dir = NULL;
258 char *data_dir = getUserGameDataDir();
259 char *tape_subdir = TAPES_DIRECTORY;
261 checked_free(tape_dir);
263 if (level_subdir != NULL)
264 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
266 tape_dir = getPath2(data_dir, tape_subdir);
271 static char *getSolutionTapeDir(void)
273 static char *tape_dir = NULL;
274 char *data_dir = getCurrentLevelDir();
275 char *tape_subdir = TAPES_DIRECTORY;
277 checked_free(tape_dir);
279 tape_dir = getPath2(data_dir, tape_subdir);
284 static char *getDefaultGraphicsDir(char *graphics_subdir)
286 static char *graphics_dir = NULL;
288 if (graphics_subdir == NULL)
289 return options.graphics_directory;
291 checked_free(graphics_dir);
293 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
298 static char *getDefaultSoundsDir(char *sounds_subdir)
300 static char *sounds_dir = NULL;
302 if (sounds_subdir == NULL)
303 return options.sounds_directory;
305 checked_free(sounds_dir);
307 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
312 static char *getDefaultMusicDir(char *music_subdir)
314 static char *music_dir = NULL;
316 if (music_subdir == NULL)
317 return options.music_directory;
319 checked_free(music_dir);
321 music_dir = getPath2(options.music_directory, music_subdir);
326 static char *getClassicArtworkSet(int type)
328 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
329 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
330 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
333 static char *getClassicArtworkDir(int type)
335 return (type == TREE_TYPE_GRAPHICS_DIR ?
336 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
337 type == TREE_TYPE_SOUNDS_DIR ?
338 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
339 type == TREE_TYPE_MUSIC_DIR ?
340 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
343 char *getUserGraphicsDir(void)
345 static char *usergraphics_dir = NULL;
347 if (usergraphics_dir == NULL)
348 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
350 return usergraphics_dir;
353 char *getUserSoundsDir(void)
355 static char *usersounds_dir = NULL;
357 if (usersounds_dir == NULL)
358 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
360 return usersounds_dir;
363 char *getUserMusicDir(void)
365 static char *usermusic_dir = NULL;
367 if (usermusic_dir == NULL)
368 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
370 return usermusic_dir;
373 static char *getSetupArtworkDir(TreeInfo *ti)
375 static char *artwork_dir = NULL;
380 checked_free(artwork_dir);
382 artwork_dir = getPath2(ti->basepath, ti->fullpath);
387 char *setLevelArtworkDir(TreeInfo *ti)
389 char **artwork_path_ptr, **artwork_set_ptr;
390 TreeInfo *level_artwork;
392 if (ti == NULL || leveldir_current == NULL)
395 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
396 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
398 checked_free(*artwork_path_ptr);
400 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
402 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
407 No (or non-existing) artwork configured in "levelinfo.conf". This would
408 normally result in using the artwork configured in the setup menu. But
409 if an artwork subdirectory exists (which might contain custom artwork
410 or an artwork configuration file), this level artwork must be treated
411 as relative to the default "classic" artwork, not to the artwork that
412 is currently configured in the setup menu.
414 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
415 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
416 the real "classic" artwork from the original R'n'D (like "gfx_classic").
419 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
421 checked_free(*artwork_set_ptr);
423 if (directoryExists(dir))
425 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
426 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
430 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
431 *artwork_set_ptr = NULL;
437 return *artwork_set_ptr;
440 static char *getLevelArtworkSet(int type)
442 if (leveldir_current == NULL)
445 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
448 static char *getLevelArtworkDir(int type)
450 if (leveldir_current == NULL)
451 return UNDEFINED_FILENAME;
453 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
456 char *getProgramMainDataPath(char *command_filename, char *base_path)
458 // check if the program's main data base directory is configured
459 if (!strEqual(base_path, "."))
462 /* if the program is configured to start from current directory (default),
463 determine program package directory from program binary (some versions
464 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
465 set the current working directory to the program package directory) */
466 char *main_data_path = getBasePath(command_filename);
468 #if defined(PLATFORM_MACOSX)
469 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
471 char *main_data_path_old = main_data_path;
473 // cut relative path to Mac OS X application binary directory from path
474 main_data_path[strlen(main_data_path) -
475 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
477 // cut trailing path separator from path (but not if path is root directory)
478 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
479 main_data_path[strlen(main_data_path) - 1] = '\0';
481 // replace empty path with current directory
482 if (strEqual(main_data_path, ""))
483 main_data_path = ".";
485 // add relative path to Mac OS X application resources directory to path
486 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
488 free(main_data_path_old);
492 return main_data_path;
495 char *getProgramConfigFilename(char *command_filename)
497 char *command_filename_1 = getStringCopy(command_filename);
499 // strip trailing executable suffix from command filename
500 if (strSuffix(command_filename_1, ".exe"))
501 command_filename_1[strlen(command_filename_1) - 4] = '\0';
503 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
504 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
506 char *command_basepath = getBasePath(command_filename);
507 char *command_basename = getBaseNameNoSuffix(command_filename);
508 char *command_filename_2 = getPath2(command_basepath, command_basename);
510 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
511 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
512 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
514 // 1st try: look for config file that exactly matches the binary filename
515 if (fileExists(config_filename_1))
516 return config_filename_1;
518 // 2nd try: look for config file that matches binary filename without suffix
519 if (fileExists(config_filename_2))
520 return config_filename_2;
522 // 3rd try: return setup config filename in global program config directory
523 return config_filename_3;
526 char *getTapeFilename(int nr)
528 static char *filename = NULL;
529 char basename[MAX_FILENAME_LEN];
531 checked_free(filename);
533 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
534 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
539 char *getSolutionTapeFilename(int nr)
541 static char *filename = NULL;
542 char basename[MAX_FILENAME_LEN];
544 checked_free(filename);
546 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
547 filename = getPath2(getSolutionTapeDir(), basename);
549 if (!fileExists(filename))
551 static char *filename_sln = NULL;
553 checked_free(filename_sln);
555 sprintf(basename, "%03d.sln", nr);
556 filename_sln = getPath2(getSolutionTapeDir(), basename);
558 if (fileExists(filename_sln))
565 char *getScoreFilename(int nr)
567 static char *filename = NULL;
568 char basename[MAX_FILENAME_LEN];
570 checked_free(filename);
572 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
574 // used instead of "leveldir_current->subdir" (for network games)
575 filename = getPath2(getScoreDir(levelset.identifier), basename);
580 char *getSetupFilename(void)
582 static char *filename = NULL;
584 checked_free(filename);
586 filename = getPath2(getSetupDir(), SETUP_FILENAME);
591 char *getDefaultSetupFilename(void)
593 return program.config_filename;
596 char *getEditorSetupFilename(void)
598 static char *filename = NULL;
600 checked_free(filename);
601 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
603 if (fileExists(filename))
606 checked_free(filename);
607 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
612 char *getHelpAnimFilename(void)
614 static char *filename = NULL;
616 checked_free(filename);
618 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
623 char *getHelpTextFilename(void)
625 static char *filename = NULL;
627 checked_free(filename);
629 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
634 char *getLevelSetInfoFilename(void)
636 static char *filename = NULL;
651 for (i = 0; basenames[i] != NULL; i++)
653 checked_free(filename);
654 filename = getPath2(getCurrentLevelDir(), basenames[i]);
656 if (fileExists(filename))
663 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
665 static char basename[32];
667 sprintf(basename, "%s_%d.txt",
668 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
673 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
675 static char *filename = NULL;
677 boolean skip_setup_artwork = FALSE;
679 checked_free(filename);
681 basename = getLevelSetTitleMessageBasename(nr, initial);
683 if (!gfx.override_level_graphics)
685 // 1st try: look for special artwork in current level series directory
686 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
687 if (fileExists(filename))
692 // 2nd try: look for message file in current level set directory
693 filename = getPath2(getCurrentLevelDir(), basename);
694 if (fileExists(filename))
699 // check if there is special artwork configured in level series config
700 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
702 // 3rd try: look for special artwork configured in level series config
703 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
704 if (fileExists(filename))
709 // take missing artwork configured in level set config from default
710 skip_setup_artwork = TRUE;
714 if (!skip_setup_artwork)
716 // 4th try: look for special artwork in configured artwork directory
717 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
718 if (fileExists(filename))
724 // 5th try: look for default artwork in new default artwork directory
725 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
726 if (fileExists(filename))
731 // 6th try: look for default artwork in old default artwork directory
732 filename = getPath2(options.graphics_directory, basename);
733 if (fileExists(filename))
736 return NULL; // cannot find specified artwork file anywhere
739 static char *getCorrectedArtworkBasename(char *basename)
744 char *getCustomImageFilename(char *basename)
746 static char *filename = NULL;
747 boolean skip_setup_artwork = FALSE;
749 checked_free(filename);
751 basename = getCorrectedArtworkBasename(basename);
753 if (!gfx.override_level_graphics)
755 // 1st try: look for special artwork in current level series directory
756 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
757 if (fileExists(filename))
762 // check if there is special artwork configured in level series config
763 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
765 // 2nd try: look for special artwork configured in level series config
766 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
767 if (fileExists(filename))
772 // take missing artwork configured in level set config from default
773 skip_setup_artwork = TRUE;
777 if (!skip_setup_artwork)
779 // 3rd try: look for special artwork in configured artwork directory
780 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
781 if (fileExists(filename))
787 // 4th try: look for default artwork in new default artwork directory
788 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
789 if (fileExists(filename))
794 // 5th try: look for default artwork in old default artwork directory
795 filename = getImg2(options.graphics_directory, basename);
796 if (fileExists(filename))
799 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
804 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
807 // 6th try: look for fallback artwork in old default artwork directory
808 // (needed to prevent errors when trying to access unused artwork files)
809 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
810 if (fileExists(filename))
814 return NULL; // cannot find specified artwork file anywhere
817 char *getCustomSoundFilename(char *basename)
819 static char *filename = NULL;
820 boolean skip_setup_artwork = FALSE;
822 checked_free(filename);
824 basename = getCorrectedArtworkBasename(basename);
826 if (!gfx.override_level_sounds)
828 // 1st try: look for special artwork in current level series directory
829 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
830 if (fileExists(filename))
835 // check if there is special artwork configured in level series config
836 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
838 // 2nd try: look for special artwork configured in level series config
839 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
840 if (fileExists(filename))
845 // take missing artwork configured in level set config from default
846 skip_setup_artwork = TRUE;
850 if (!skip_setup_artwork)
852 // 3rd try: look for special artwork in configured artwork directory
853 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
854 if (fileExists(filename))
860 // 4th try: look for default artwork in new default artwork directory
861 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
862 if (fileExists(filename))
867 // 5th try: look for default artwork in old default artwork directory
868 filename = getPath2(options.sounds_directory, basename);
869 if (fileExists(filename))
872 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
877 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
880 // 6th try: look for fallback artwork in old default artwork directory
881 // (needed to prevent errors when trying to access unused artwork files)
882 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
883 if (fileExists(filename))
887 return NULL; // cannot find specified artwork file anywhere
890 char *getCustomMusicFilename(char *basename)
892 static char *filename = NULL;
893 boolean skip_setup_artwork = FALSE;
895 checked_free(filename);
897 basename = getCorrectedArtworkBasename(basename);
899 if (!gfx.override_level_music)
901 // 1st try: look for special artwork in current level series directory
902 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
903 if (fileExists(filename))
908 // check if there is special artwork configured in level series config
909 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
911 // 2nd try: look for special artwork configured in level series config
912 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
913 if (fileExists(filename))
918 // take missing artwork configured in level set config from default
919 skip_setup_artwork = TRUE;
923 if (!skip_setup_artwork)
925 // 3rd try: look for special artwork in configured artwork directory
926 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
927 if (fileExists(filename))
933 // 4th try: look for default artwork in new default artwork directory
934 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
935 if (fileExists(filename))
940 // 5th try: look for default artwork in old default artwork directory
941 filename = getPath2(options.music_directory, basename);
942 if (fileExists(filename))
945 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
950 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
953 // 6th try: look for fallback artwork in old default artwork directory
954 // (needed to prevent errors when trying to access unused artwork files)
955 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
956 if (fileExists(filename))
960 return NULL; // cannot find specified artwork file anywhere
963 char *getCustomArtworkFilename(char *basename, int type)
965 if (type == ARTWORK_TYPE_GRAPHICS)
966 return getCustomImageFilename(basename);
967 else if (type == ARTWORK_TYPE_SOUNDS)
968 return getCustomSoundFilename(basename);
969 else if (type == ARTWORK_TYPE_MUSIC)
970 return getCustomMusicFilename(basename);
972 return UNDEFINED_FILENAME;
975 char *getCustomArtworkConfigFilename(int type)
977 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
980 char *getCustomArtworkLevelConfigFilename(int type)
982 static char *filename = NULL;
984 checked_free(filename);
986 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
991 char *getCustomMusicDirectory(void)
993 static char *directory = NULL;
994 boolean skip_setup_artwork = FALSE;
996 checked_free(directory);
998 if (!gfx.override_level_music)
1000 // 1st try: look for special artwork in current level series directory
1001 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1002 if (directoryExists(directory))
1007 // check if there is special artwork configured in level series config
1008 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1010 // 2nd try: look for special artwork configured in level series config
1011 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1012 if (directoryExists(directory))
1017 // take missing artwork configured in level set config from default
1018 skip_setup_artwork = TRUE;
1022 if (!skip_setup_artwork)
1024 // 3rd try: look for special artwork in configured artwork directory
1025 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1026 if (directoryExists(directory))
1032 // 4th try: look for default artwork in new default artwork directory
1033 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1034 if (directoryExists(directory))
1039 // 5th try: look for default artwork in old default artwork directory
1040 directory = getStringCopy(options.music_directory);
1041 if (directoryExists(directory))
1044 return NULL; // cannot find specified artwork file anywhere
1047 void InitTapeDirectory(char *level_subdir)
1049 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1050 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1051 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1054 void InitScoreDirectory(char *level_subdir)
1056 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1058 if (program.global_scores)
1059 createDirectory(getCommonDataDir(), "common data", permissions);
1061 createDirectory(getUserGameDataDir(), "user data", permissions);
1063 createDirectory(getScoreDir(NULL), "main score", permissions);
1064 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1067 static void SaveUserLevelInfo(void);
1069 void InitUserLevelDirectory(char *level_subdir)
1071 if (!directoryExists(getUserLevelDir(level_subdir)))
1073 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1074 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1075 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1077 SaveUserLevelInfo();
1081 void InitNetworkLevelDirectory(char *level_subdir)
1083 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1085 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1086 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1087 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1088 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1092 void InitLevelSetupDirectory(char *level_subdir)
1094 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1095 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1096 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1099 static void InitCacheDirectory(void)
1101 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1102 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1106 // ----------------------------------------------------------------------------
1107 // some functions to handle lists of level and artwork directories
1108 // ----------------------------------------------------------------------------
1110 TreeInfo *newTreeInfo(void)
1112 return checked_calloc(sizeof(TreeInfo));
1115 TreeInfo *newTreeInfo_setDefaults(int type)
1117 TreeInfo *ti = newTreeInfo();
1119 setTreeInfoToDefaults(ti, type);
1124 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1126 node_new->next = *node_first;
1127 *node_first = node_new;
1130 int numTreeInfo(TreeInfo *node)
1143 boolean validLevelSeries(TreeInfo *node)
1145 return (node != NULL && !node->node_group && !node->parent_link);
1148 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1153 if (node->node_group) // enter level group (step down into tree)
1154 return getFirstValidTreeInfoEntry(node->node_group);
1155 else if (node->parent_link) // skip start entry of level group
1157 if (node->next) // get first real level series entry
1158 return getFirstValidTreeInfoEntry(node->next);
1159 else // leave empty level group and go on
1160 return getFirstValidTreeInfoEntry(node->node_parent->next);
1162 else // this seems to be a regular level series
1166 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1171 if (node->node_parent == NULL) // top level group
1172 return *node->node_top;
1173 else // sub level group
1174 return node->node_parent->node_group;
1177 int numTreeInfoInGroup(TreeInfo *node)
1179 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1182 int posTreeInfo(TreeInfo *node)
1184 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1189 if (node_cmp == node)
1193 node_cmp = node_cmp->next;
1199 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1201 TreeInfo *node_default = node;
1213 return node_default;
1216 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1218 if (identifier == NULL)
1223 if (node->node_group)
1225 TreeInfo *node_group;
1227 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1232 else if (!node->parent_link)
1234 if (strEqual(identifier, node->identifier))
1244 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1245 TreeInfo *node, boolean skip_sets_without_levels)
1252 if (!node->parent_link && !node->level_group &&
1253 skip_sets_without_levels && node->levels == 0)
1254 return cloneTreeNode(node_top, node_parent, node->next,
1255 skip_sets_without_levels);
1257 node_new = getTreeInfoCopy(node); // copy complete node
1259 node_new->node_top = node_top; // correct top node link
1260 node_new->node_parent = node_parent; // correct parent node link
1262 if (node->level_group)
1263 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1264 skip_sets_without_levels);
1266 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1267 skip_sets_without_levels);
1272 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1274 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1276 *ti_new = ti_cloned;
1279 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1281 boolean settings_changed = FALSE;
1285 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1286 !strEqual(node->graphics_set, node->graphics_set_ecs))
1288 setString(&node->graphics_set, node->graphics_set_ecs);
1289 settings_changed = TRUE;
1291 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1292 !strEqual(node->graphics_set, node->graphics_set_aga))
1294 setString(&node->graphics_set, node->graphics_set_aga);
1295 settings_changed = TRUE;
1298 if (node->node_group != NULL)
1299 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1304 return settings_changed;
1307 void dumpTreeInfo(TreeInfo *node, int depth)
1311 printf("Dumping TreeInfo:\n");
1315 for (i = 0; i < (depth + 1) * 3; i++)
1318 printf("'%s' / '%s'\n", node->identifier, node->name);
1321 // use for dumping artwork info tree
1322 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1323 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1326 if (node->node_group != NULL)
1327 dumpTreeInfo(node->node_group, depth + 1);
1333 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1334 int (*compare_function)(const void *,
1337 int num_nodes = numTreeInfo(*node_first);
1338 TreeInfo **sort_array;
1339 TreeInfo *node = *node_first;
1345 // allocate array for sorting structure pointers
1346 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1348 // writing structure pointers to sorting array
1349 while (i < num_nodes && node) // double boundary check...
1351 sort_array[i] = node;
1357 // sorting the structure pointers in the sorting array
1358 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1361 // update the linkage of list elements with the sorted node array
1362 for (i = 0; i < num_nodes - 1; i++)
1363 sort_array[i]->next = sort_array[i + 1];
1364 sort_array[num_nodes - 1]->next = NULL;
1366 // update the linkage of the main list anchor pointer
1367 *node_first = sort_array[0];
1371 // now recursively sort the level group structures
1375 if (node->node_group != NULL)
1376 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1382 void sortTreeInfo(TreeInfo **node_first)
1384 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1388 // ============================================================================
1389 // some stuff from "files.c"
1390 // ============================================================================
1392 #if defined(PLATFORM_WIN32)
1394 #define S_IRGRP S_IRUSR
1397 #define S_IROTH S_IRUSR
1400 #define S_IWGRP S_IWUSR
1403 #define S_IWOTH S_IWUSR
1406 #define S_IXGRP S_IXUSR
1409 #define S_IXOTH S_IXUSR
1412 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1417 #endif // PLATFORM_WIN32
1419 // file permissions for newly written files
1420 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1421 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1422 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1424 #define MODE_W_PRIVATE (S_IWUSR)
1425 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1426 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1428 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1429 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1430 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1432 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1433 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1434 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1437 char *getHomeDir(void)
1439 static char *dir = NULL;
1441 #if defined(PLATFORM_WIN32)
1444 dir = checked_malloc(MAX_PATH + 1);
1446 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1449 #elif defined(PLATFORM_UNIX)
1452 if ((dir = getenv("HOME")) == NULL)
1456 if ((pwd = getpwuid(getuid())) != NULL)
1457 dir = getStringCopy(pwd->pw_dir);
1469 char *getCommonDataDir(void)
1471 static char *common_data_dir = NULL;
1473 #if defined(PLATFORM_WIN32)
1474 if (common_data_dir == NULL)
1476 char *dir = checked_malloc(MAX_PATH + 1);
1478 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1479 && !strEqual(dir, "")) // empty for Windows 95/98
1480 common_data_dir = getPath2(dir, program.userdata_subdir);
1482 common_data_dir = options.rw_base_directory;
1485 if (common_data_dir == NULL)
1486 common_data_dir = options.rw_base_directory;
1489 return common_data_dir;
1492 char *getPersonalDataDir(void)
1494 static char *personal_data_dir = NULL;
1496 #if defined(PLATFORM_MACOSX)
1497 if (personal_data_dir == NULL)
1498 personal_data_dir = getPath2(getHomeDir(), "Documents");
1500 if (personal_data_dir == NULL)
1501 personal_data_dir = getHomeDir();
1504 return personal_data_dir;
1507 char *getUserGameDataDir(void)
1509 static char *user_game_data_dir = NULL;
1511 #if defined(PLATFORM_ANDROID)
1512 if (user_game_data_dir == NULL)
1513 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1514 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1515 SDL_AndroidGetExternalStoragePath() :
1516 SDL_AndroidGetInternalStoragePath());
1518 if (user_game_data_dir == NULL)
1519 user_game_data_dir = getPath2(getPersonalDataDir(),
1520 program.userdata_subdir);
1523 return user_game_data_dir;
1526 char *getSetupDir(void)
1528 return getUserGameDataDir();
1531 static mode_t posix_umask(mode_t mask)
1533 #if defined(PLATFORM_UNIX)
1540 static int posix_mkdir(const char *pathname, mode_t mode)
1542 #if defined(PLATFORM_WIN32)
1543 return mkdir(pathname);
1545 return mkdir(pathname, mode);
1549 static boolean posix_process_running_setgid(void)
1551 #if defined(PLATFORM_UNIX)
1552 return (getgid() != getegid());
1558 void createDirectory(char *dir, char *text, int permission_class)
1560 if (directoryExists(dir))
1563 // leave "other" permissions in umask untouched, but ensure group parts
1564 // of USERDATA_DIR_MODE are not masked
1565 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1566 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1567 mode_t last_umask = posix_umask(0);
1568 mode_t group_umask = ~(dir_mode & S_IRWXG);
1569 int running_setgid = posix_process_running_setgid();
1571 if (permission_class == PERMS_PUBLIC)
1573 // if we're setgid, protect files against "other"
1574 // else keep umask(0) to make the dir world-writable
1577 posix_umask(last_umask & group_umask);
1579 dir_mode = DIR_PERMS_PUBLIC_ALL;
1582 if (posix_mkdir(dir, dir_mode) != 0)
1583 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1584 text, dir, strerror(errno));
1586 if (permission_class == PERMS_PUBLIC && !running_setgid)
1587 chmod(dir, dir_mode);
1589 posix_umask(last_umask); // restore previous umask
1592 void InitUserDataDirectory(void)
1594 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1597 void SetFilePermissions(char *filename, int permission_class)
1599 int running_setgid = posix_process_running_setgid();
1600 int perms = (permission_class == PERMS_PRIVATE ?
1601 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1603 if (permission_class == PERMS_PUBLIC && !running_setgid)
1604 perms = FILE_PERMS_PUBLIC_ALL;
1606 chmod(filename, perms);
1609 char *getCookie(char *file_type)
1611 static char cookie[MAX_COOKIE_LEN + 1];
1613 if (strlen(program.cookie_prefix) + 1 +
1614 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1615 return "[COOKIE ERROR]"; // should never happen
1617 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1618 program.cookie_prefix, file_type,
1619 program.version_super, program.version_major);
1624 void fprintFileHeader(FILE *file, char *basename)
1626 char *prefix = "# ";
1629 fprintf_line_with_prefix(file, prefix, sep1, 77);
1630 fprintf(file, "%s%s\n", prefix, basename);
1631 fprintf_line_with_prefix(file, prefix, sep1, 77);
1632 fprintf(file, "\n");
1635 int getFileVersionFromCookieString(const char *cookie)
1637 const char *ptr_cookie1, *ptr_cookie2;
1638 const char *pattern1 = "_FILE_VERSION_";
1639 const char *pattern2 = "?.?";
1640 const int len_cookie = strlen(cookie);
1641 const int len_pattern1 = strlen(pattern1);
1642 const int len_pattern2 = strlen(pattern2);
1643 const int len_pattern = len_pattern1 + len_pattern2;
1644 int version_super, version_major;
1646 if (len_cookie <= len_pattern)
1649 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1650 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1652 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1655 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1656 ptr_cookie2[1] != '.' ||
1657 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1660 version_super = ptr_cookie2[0] - '0';
1661 version_major = ptr_cookie2[2] - '0';
1663 return VERSION_IDENT(version_super, version_major, 0, 0);
1666 boolean checkCookieString(const char *cookie, const char *template)
1668 const char *pattern = "_FILE_VERSION_?.?";
1669 const int len_cookie = strlen(cookie);
1670 const int len_template = strlen(template);
1671 const int len_pattern = strlen(pattern);
1673 if (len_cookie != len_template)
1676 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1683 // ----------------------------------------------------------------------------
1684 // setup file list and hash handling functions
1685 // ----------------------------------------------------------------------------
1687 char *getFormattedSetupEntry(char *token, char *value)
1690 static char entry[MAX_LINE_LEN];
1692 // if value is an empty string, just return token without value
1696 // start with the token and some spaces to format output line
1697 sprintf(entry, "%s:", token);
1698 for (i = strlen(entry); i < token_value_position; i++)
1701 // continue with the token's value
1702 strcat(entry, value);
1707 SetupFileList *newSetupFileList(char *token, char *value)
1709 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1711 new->token = getStringCopy(token);
1712 new->value = getStringCopy(value);
1719 void freeSetupFileList(SetupFileList *list)
1724 checked_free(list->token);
1725 checked_free(list->value);
1728 freeSetupFileList(list->next);
1733 char *getListEntry(SetupFileList *list, char *token)
1738 if (strEqual(list->token, token))
1741 return getListEntry(list->next, token);
1744 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1749 if (strEqual(list->token, token))
1751 checked_free(list->value);
1753 list->value = getStringCopy(value);
1757 else if (list->next == NULL)
1758 return (list->next = newSetupFileList(token, value));
1760 return setListEntry(list->next, token, value);
1763 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1768 if (list->next == NULL)
1769 return (list->next = newSetupFileList(token, value));
1771 return addListEntry(list->next, token, value);
1774 #if ENABLE_UNUSED_CODE
1776 static void printSetupFileList(SetupFileList *list)
1781 printf("token: '%s'\n", list->token);
1782 printf("value: '%s'\n", list->value);
1784 printSetupFileList(list->next);
1790 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1791 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1792 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1793 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1795 #define insert_hash_entry hashtable_insert
1796 #define search_hash_entry hashtable_search
1797 #define change_hash_entry hashtable_change
1798 #define remove_hash_entry hashtable_remove
1801 unsigned int get_hash_from_key(void *key)
1806 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1807 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1808 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1809 it works better than many other constants, prime or not) has never been
1810 adequately explained.
1812 If you just want to have a good hash function, and cannot wait, djb2
1813 is one of the best string hash functions i know. It has excellent
1814 distribution and speed on many different sets of keys and table sizes.
1815 You are not likely to do better with one of the "well known" functions
1816 such as PJW, K&R, etc.
1818 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1821 char *str = (char *)key;
1822 unsigned int hash = 5381;
1825 while ((c = *str++))
1826 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1831 static int keys_are_equal(void *key1, void *key2)
1833 return (strEqual((char *)key1, (char *)key2));
1836 SetupFileHash *newSetupFileHash(void)
1838 SetupFileHash *new_hash =
1839 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1841 if (new_hash == NULL)
1842 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1847 void freeSetupFileHash(SetupFileHash *hash)
1852 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1855 char *getHashEntry(SetupFileHash *hash, char *token)
1860 return search_hash_entry(hash, token);
1863 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1870 value_copy = getStringCopy(value);
1872 // change value; if it does not exist, insert it as new
1873 if (!change_hash_entry(hash, token, value_copy))
1874 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1875 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1878 char *removeHashEntry(SetupFileHash *hash, char *token)
1883 return remove_hash_entry(hash, token);
1886 #if ENABLE_UNUSED_CODE
1888 static void printSetupFileHash(SetupFileHash *hash)
1890 BEGIN_HASH_ITERATION(hash, itr)
1892 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1893 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1895 END_HASH_ITERATION(hash, itr)
1900 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1901 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1902 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1904 static boolean token_value_separator_found = FALSE;
1905 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1906 static boolean token_value_separator_warning = FALSE;
1908 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1909 static boolean token_already_exists_warning = FALSE;
1912 static boolean getTokenValueFromSetupLineExt(char *line,
1913 char **token_ptr, char **value_ptr,
1914 char *filename, char *line_raw,
1916 boolean separator_required)
1918 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1919 char *token, *value, *line_ptr;
1921 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1922 if (line_raw == NULL)
1924 strncpy(line_copy, line, MAX_LINE_LEN);
1925 line_copy[MAX_LINE_LEN] = '\0';
1928 strcpy(line_raw_copy, line_copy);
1929 line_raw = line_raw_copy;
1932 // cut trailing comment from input line
1933 for (line_ptr = line; *line_ptr; line_ptr++)
1935 if (*line_ptr == '#')
1942 // cut trailing whitespaces from input line
1943 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1944 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1947 // ignore empty lines
1951 // cut leading whitespaces from token
1952 for (token = line; *token; token++)
1953 if (*token != ' ' && *token != '\t')
1956 // start with empty value as reliable default
1959 token_value_separator_found = FALSE;
1961 // find end of token to determine start of value
1962 for (line_ptr = token; *line_ptr; line_ptr++)
1964 // first look for an explicit token/value separator, like ':' or '='
1965 if (*line_ptr == ':' || *line_ptr == '=')
1967 *line_ptr = '\0'; // terminate token string
1968 value = line_ptr + 1; // set beginning of value
1970 token_value_separator_found = TRUE;
1976 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1977 // fallback: if no token/value separator found, also allow whitespaces
1978 if (!token_value_separator_found && !separator_required)
1980 for (line_ptr = token; *line_ptr; line_ptr++)
1982 if (*line_ptr == ' ' || *line_ptr == '\t')
1984 *line_ptr = '\0'; // terminate token string
1985 value = line_ptr + 1; // set beginning of value
1987 token_value_separator_found = TRUE;
1993 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1994 if (token_value_separator_found)
1996 if (!token_value_separator_warning)
1998 Error(ERR_INFO_LINE, "-");
2000 if (filename != NULL)
2002 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2003 Error(ERR_INFO, "- config file: '%s'", filename);
2007 Error(ERR_WARN, "missing token/value separator(s):");
2010 token_value_separator_warning = TRUE;
2013 if (filename != NULL)
2014 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2016 Error(ERR_INFO, "- line: '%s'", line_raw);
2022 // cut trailing whitespaces from token
2023 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2024 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2027 // cut leading whitespaces from value
2028 for (; *value; value++)
2029 if (*value != ' ' && *value != '\t')
2038 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2040 // while the internal (old) interface does not require a token/value
2041 // separator (for downwards compatibility with existing files which
2042 // don't use them), it is mandatory for the external (new) interface
2044 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2047 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2048 boolean top_recursion_level, boolean is_hash)
2050 static SetupFileHash *include_filename_hash = NULL;
2051 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2052 char *token, *value, *line_ptr;
2053 void *insert_ptr = NULL;
2054 boolean read_continued_line = FALSE;
2056 int line_nr = 0, token_count = 0, include_count = 0;
2058 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2059 token_value_separator_warning = FALSE;
2062 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2063 token_already_exists_warning = FALSE;
2066 if (!(file = openFile(filename, MODE_READ)))
2068 #if DEBUG_NO_CONFIG_FILE
2069 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2075 // use "insert pointer" to store list end for constant insertion complexity
2077 insert_ptr = setup_file_data;
2079 // on top invocation, create hash to mark included files (to prevent loops)
2080 if (top_recursion_level)
2081 include_filename_hash = newSetupFileHash();
2083 // mark this file as already included (to prevent including it again)
2084 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2086 while (!checkEndOfFile(file))
2088 // read next line of input file
2089 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2092 // check if line was completely read and is terminated by line break
2093 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2096 // cut trailing line break (this can be newline and/or carriage return)
2097 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2098 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2101 // copy raw input line for later use (mainly debugging output)
2102 strcpy(line_raw, line);
2104 if (read_continued_line)
2106 // append new line to existing line, if there is enough space
2107 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2108 strcat(previous_line, line_ptr);
2110 strcpy(line, previous_line); // copy storage buffer to line
2112 read_continued_line = FALSE;
2115 // if the last character is '\', continue at next line
2116 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2118 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2119 strcpy(previous_line, line); // copy line to storage buffer
2121 read_continued_line = TRUE;
2126 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2127 line_raw, line_nr, FALSE))
2132 if (strEqual(token, "include"))
2134 if (getHashEntry(include_filename_hash, value) == NULL)
2136 char *basepath = getBasePath(filename);
2137 char *basename = getBaseName(value);
2138 char *filename_include = getPath2(basepath, basename);
2140 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2144 free(filename_include);
2150 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2157 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2159 getHashEntry((SetupFileHash *)setup_file_data, token);
2161 if (old_value != NULL)
2163 if (!token_already_exists_warning)
2165 Error(ERR_INFO_LINE, "-");
2166 Error(ERR_WARN, "duplicate token(s) found in config file:");
2167 Error(ERR_INFO, "- config file: '%s'", filename);
2169 token_already_exists_warning = TRUE;
2172 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2173 Error(ERR_INFO, " old value: '%s'", old_value);
2174 Error(ERR_INFO, " new value: '%s'", value);
2178 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2182 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2192 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2193 if (token_value_separator_warning)
2194 Error(ERR_INFO_LINE, "-");
2197 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2198 if (token_already_exists_warning)
2199 Error(ERR_INFO_LINE, "-");
2202 if (token_count == 0 && include_count == 0)
2203 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2205 if (top_recursion_level)
2206 freeSetupFileHash(include_filename_hash);
2211 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2215 if (!(file = fopen(filename, MODE_WRITE)))
2217 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2222 BEGIN_HASH_ITERATION(hash, itr)
2224 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2225 HASH_ITERATION_VALUE(itr)));
2227 END_HASH_ITERATION(hash, itr)
2232 SetupFileList *loadSetupFileList(char *filename)
2234 SetupFileList *setup_file_list = newSetupFileList("", "");
2235 SetupFileList *first_valid_list_entry;
2237 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2239 freeSetupFileList(setup_file_list);
2244 first_valid_list_entry = setup_file_list->next;
2246 // free empty list header
2247 setup_file_list->next = NULL;
2248 freeSetupFileList(setup_file_list);
2250 return first_valid_list_entry;
2253 SetupFileHash *loadSetupFileHash(char *filename)
2255 SetupFileHash *setup_file_hash = newSetupFileHash();
2257 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2259 freeSetupFileHash(setup_file_hash);
2264 return setup_file_hash;
2268 // ============================================================================
2270 // ============================================================================
2272 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2273 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2274 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2276 // level directory info
2277 #define LEVELINFO_TOKEN_IDENTIFIER 0
2278 #define LEVELINFO_TOKEN_NAME 1
2279 #define LEVELINFO_TOKEN_NAME_SORTING 2
2280 #define LEVELINFO_TOKEN_AUTHOR 3
2281 #define LEVELINFO_TOKEN_YEAR 4
2282 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2283 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2284 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2285 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2286 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2287 #define LEVELINFO_TOKEN_TESTED_BY 10
2288 #define LEVELINFO_TOKEN_LEVELS 11
2289 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2290 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2291 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2292 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2293 #define LEVELINFO_TOKEN_READONLY 16
2294 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2295 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2296 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2297 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2298 #define LEVELINFO_TOKEN_MUSIC_SET 21
2299 #define LEVELINFO_TOKEN_FILENAME 22
2300 #define LEVELINFO_TOKEN_FILETYPE 23
2301 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2302 #define LEVELINFO_TOKEN_HANDICAP 25
2303 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2305 #define NUM_LEVELINFO_TOKENS 27
2307 static LevelDirTree ldi;
2309 static struct TokenInfo levelinfo_tokens[] =
2311 // level directory info
2312 { TYPE_STRING, &ldi.identifier, "identifier" },
2313 { TYPE_STRING, &ldi.name, "name" },
2314 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2315 { TYPE_STRING, &ldi.author, "author" },
2316 { TYPE_STRING, &ldi.year, "year" },
2317 { TYPE_STRING, &ldi.program_title, "program_title" },
2318 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2319 { TYPE_STRING, &ldi.program_company, "program_company" },
2320 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2321 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2322 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2323 { TYPE_INTEGER, &ldi.levels, "levels" },
2324 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2325 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2326 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2327 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2328 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2329 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2330 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2331 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2332 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2333 { TYPE_STRING, &ldi.music_set, "music_set" },
2334 { TYPE_STRING, &ldi.level_filename, "filename" },
2335 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2336 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2337 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2338 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2341 static struct TokenInfo artworkinfo_tokens[] =
2343 // artwork directory info
2344 { TYPE_STRING, &ldi.identifier, "identifier" },
2345 { TYPE_STRING, &ldi.subdir, "subdir" },
2346 { TYPE_STRING, &ldi.name, "name" },
2347 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2348 { TYPE_STRING, &ldi.author, "author" },
2349 { TYPE_STRING, &ldi.program_title, "program_title" },
2350 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2351 { TYPE_STRING, &ldi.program_company, "program_company" },
2352 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2353 { TYPE_STRING, &ldi.basepath, "basepath" },
2354 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2355 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2356 { TYPE_INTEGER, &ldi.color, "color" },
2357 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2362 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2366 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2367 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2368 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2369 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2372 ti->node_parent = NULL;
2373 ti->node_group = NULL;
2380 ti->fullpath = NULL;
2381 ti->basepath = NULL;
2382 ti->identifier = NULL;
2383 ti->name = getStringCopy(ANONYMOUS_NAME);
2384 ti->name_sorting = NULL;
2385 ti->author = getStringCopy(ANONYMOUS_NAME);
2388 ti->program_title = NULL;
2389 ti->program_copyright = NULL;
2390 ti->program_company = NULL;
2392 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2393 ti->latest_engine = FALSE; // default: get from level
2394 ti->parent_link = FALSE;
2395 ti->in_user_dir = FALSE;
2396 ti->user_defined = FALSE;
2398 ti->class_desc = NULL;
2400 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2402 if (ti->type == TREE_TYPE_LEVEL_DIR)
2404 ti->imported_from = NULL;
2405 ti->imported_by = NULL;
2406 ti->tested_by = NULL;
2408 ti->graphics_set_ecs = NULL;
2409 ti->graphics_set_aga = NULL;
2410 ti->graphics_set = NULL;
2411 ti->sounds_set = NULL;
2412 ti->music_set = NULL;
2413 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2414 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2415 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2417 ti->level_filename = NULL;
2418 ti->level_filetype = NULL;
2420 ti->special_flags = NULL;
2423 ti->first_level = 0;
2425 ti->level_group = FALSE;
2426 ti->handicap_level = 0;
2427 ti->readonly = TRUE;
2428 ti->handicap = TRUE;
2429 ti->skip_levels = FALSE;
2433 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2437 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2439 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2444 // copy all values from the parent structure
2446 ti->type = parent->type;
2448 ti->node_top = parent->node_top;
2449 ti->node_parent = parent;
2450 ti->node_group = NULL;
2457 ti->fullpath = NULL;
2458 ti->basepath = NULL;
2459 ti->identifier = NULL;
2460 ti->name = getStringCopy(ANONYMOUS_NAME);
2461 ti->name_sorting = NULL;
2462 ti->author = getStringCopy(parent->author);
2463 ti->year = getStringCopy(parent->year);
2465 ti->program_title = getStringCopy(parent->program_title);
2466 ti->program_copyright = getStringCopy(parent->program_copyright);
2467 ti->program_company = getStringCopy(parent->program_company);
2469 ti->sort_priority = parent->sort_priority;
2470 ti->latest_engine = parent->latest_engine;
2471 ti->parent_link = FALSE;
2472 ti->in_user_dir = parent->in_user_dir;
2473 ti->user_defined = parent->user_defined;
2474 ti->color = parent->color;
2475 ti->class_desc = getStringCopy(parent->class_desc);
2477 ti->infotext = getStringCopy(parent->infotext);
2479 if (ti->type == TREE_TYPE_LEVEL_DIR)
2481 ti->imported_from = getStringCopy(parent->imported_from);
2482 ti->imported_by = getStringCopy(parent->imported_by);
2483 ti->tested_by = getStringCopy(parent->tested_by);
2485 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2486 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2487 ti->graphics_set = getStringCopy(parent->graphics_set);
2488 ti->sounds_set = getStringCopy(parent->sounds_set);
2489 ti->music_set = getStringCopy(parent->music_set);
2490 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2492 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2494 ti->level_filename = getStringCopy(parent->level_filename);
2495 ti->level_filetype = getStringCopy(parent->level_filetype);
2497 ti->special_flags = getStringCopy(parent->special_flags);
2499 ti->levels = parent->levels;
2500 ti->first_level = parent->first_level;
2501 ti->last_level = parent->last_level;
2502 ti->level_group = FALSE;
2503 ti->handicap_level = parent->handicap_level;
2504 ti->readonly = parent->readonly;
2505 ti->handicap = parent->handicap;
2506 ti->skip_levels = parent->skip_levels;
2510 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2512 TreeInfo *ti_copy = newTreeInfo();
2514 // copy all values from the original structure
2516 ti_copy->type = ti->type;
2518 ti_copy->node_top = ti->node_top;
2519 ti_copy->node_parent = ti->node_parent;
2520 ti_copy->node_group = ti->node_group;
2521 ti_copy->next = ti->next;
2523 ti_copy->cl_first = ti->cl_first;
2524 ti_copy->cl_cursor = ti->cl_cursor;
2526 ti_copy->subdir = getStringCopy(ti->subdir);
2527 ti_copy->fullpath = getStringCopy(ti->fullpath);
2528 ti_copy->basepath = getStringCopy(ti->basepath);
2529 ti_copy->identifier = getStringCopy(ti->identifier);
2530 ti_copy->name = getStringCopy(ti->name);
2531 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2532 ti_copy->author = getStringCopy(ti->author);
2533 ti_copy->year = getStringCopy(ti->year);
2535 ti_copy->program_title = getStringCopy(ti->program_title);
2536 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2537 ti_copy->program_company = getStringCopy(ti->program_company);
2539 ti_copy->imported_from = getStringCopy(ti->imported_from);
2540 ti_copy->imported_by = getStringCopy(ti->imported_by);
2541 ti_copy->tested_by = getStringCopy(ti->tested_by);
2543 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2544 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2545 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2546 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2547 ti_copy->music_set = getStringCopy(ti->music_set);
2548 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2549 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2550 ti_copy->music_path = getStringCopy(ti->music_path);
2552 ti_copy->level_filename = getStringCopy(ti->level_filename);
2553 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2555 ti_copy->special_flags = getStringCopy(ti->special_flags);
2557 ti_copy->levels = ti->levels;
2558 ti_copy->first_level = ti->first_level;
2559 ti_copy->last_level = ti->last_level;
2560 ti_copy->sort_priority = ti->sort_priority;
2562 ti_copy->latest_engine = ti->latest_engine;
2564 ti_copy->level_group = ti->level_group;
2565 ti_copy->parent_link = ti->parent_link;
2566 ti_copy->in_user_dir = ti->in_user_dir;
2567 ti_copy->user_defined = ti->user_defined;
2568 ti_copy->readonly = ti->readonly;
2569 ti_copy->handicap = ti->handicap;
2570 ti_copy->skip_levels = ti->skip_levels;
2572 ti_copy->color = ti->color;
2573 ti_copy->class_desc = getStringCopy(ti->class_desc);
2574 ti_copy->handicap_level = ti->handicap_level;
2576 ti_copy->infotext = getStringCopy(ti->infotext);
2581 void freeTreeInfo(TreeInfo *ti)
2586 checked_free(ti->subdir);
2587 checked_free(ti->fullpath);
2588 checked_free(ti->basepath);
2589 checked_free(ti->identifier);
2591 checked_free(ti->name);
2592 checked_free(ti->name_sorting);
2593 checked_free(ti->author);
2594 checked_free(ti->year);
2596 checked_free(ti->program_title);
2597 checked_free(ti->program_copyright);
2598 checked_free(ti->program_company);
2600 checked_free(ti->class_desc);
2602 checked_free(ti->infotext);
2604 if (ti->type == TREE_TYPE_LEVEL_DIR)
2606 checked_free(ti->imported_from);
2607 checked_free(ti->imported_by);
2608 checked_free(ti->tested_by);
2610 checked_free(ti->graphics_set_ecs);
2611 checked_free(ti->graphics_set_aga);
2612 checked_free(ti->graphics_set);
2613 checked_free(ti->sounds_set);
2614 checked_free(ti->music_set);
2616 checked_free(ti->graphics_path);
2617 checked_free(ti->sounds_path);
2618 checked_free(ti->music_path);
2620 checked_free(ti->level_filename);
2621 checked_free(ti->level_filetype);
2623 checked_free(ti->special_flags);
2626 // recursively free child node
2628 freeTreeInfo(ti->node_group);
2630 // recursively free next node
2632 freeTreeInfo(ti->next);
2637 void setSetupInfo(struct TokenInfo *token_info,
2638 int token_nr, char *token_value)
2640 int token_type = token_info[token_nr].type;
2641 void *setup_value = token_info[token_nr].value;
2643 if (token_value == NULL)
2646 // set setup field to corresponding token value
2651 *(boolean *)setup_value = get_boolean_from_string(token_value);
2655 *(int *)setup_value = get_switch3_from_string(token_value);
2659 *(Key *)setup_value = getKeyFromKeyName(token_value);
2663 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2667 *(int *)setup_value = get_integer_from_string(token_value);
2671 checked_free(*(char **)setup_value);
2672 *(char **)setup_value = getStringCopy(token_value);
2676 *(int *)setup_value = get_player_nr_from_string(token_value);
2684 static int compareTreeInfoEntries(const void *object1, const void *object2)
2686 const TreeInfo *entry1 = *((TreeInfo **)object1);
2687 const TreeInfo *entry2 = *((TreeInfo **)object2);
2688 int class_sorting1 = 0, class_sorting2 = 0;
2691 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2693 class_sorting1 = LEVELSORTING(entry1);
2694 class_sorting2 = LEVELSORTING(entry2);
2696 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2697 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2698 entry1->type == TREE_TYPE_MUSIC_DIR)
2700 class_sorting1 = ARTWORKSORTING(entry1);
2701 class_sorting2 = ARTWORKSORTING(entry2);
2704 if (entry1->parent_link || entry2->parent_link)
2705 compare_result = (entry1->parent_link ? -1 : +1);
2706 else if (entry1->sort_priority == entry2->sort_priority)
2708 char *name1 = getStringToLower(entry1->name_sorting);
2709 char *name2 = getStringToLower(entry2->name_sorting);
2711 compare_result = strcmp(name1, name2);
2716 else if (class_sorting1 == class_sorting2)
2717 compare_result = entry1->sort_priority - entry2->sort_priority;
2719 compare_result = class_sorting1 - class_sorting2;
2721 return compare_result;
2724 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2728 if (node_parent == NULL)
2731 ti_new = newTreeInfo();
2732 setTreeInfoToDefaults(ti_new, node_parent->type);
2734 ti_new->node_parent = node_parent;
2735 ti_new->parent_link = TRUE;
2737 setString(&ti_new->identifier, node_parent->identifier);
2738 setString(&ti_new->name, ".. (parent directory)");
2739 setString(&ti_new->name_sorting, ti_new->name);
2741 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2742 setString(&ti_new->fullpath, node_parent->fullpath);
2744 ti_new->sort_priority = node_parent->sort_priority;
2745 ti_new->latest_engine = node_parent->latest_engine;
2747 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2749 pushTreeInfo(&node_parent->node_group, ti_new);
2754 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2756 TreeInfo *ti_new, *ti_new2;
2758 if (node_first == NULL)
2761 ti_new = newTreeInfo();
2762 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2764 ti_new->node_parent = NULL;
2765 ti_new->parent_link = FALSE;
2767 setString(&ti_new->identifier, node_first->identifier);
2768 setString(&ti_new->name, "level sets");
2769 setString(&ti_new->name_sorting, ti_new->name);
2771 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2772 setString(&ti_new->fullpath, ".");
2774 ti_new->sort_priority = node_first->sort_priority;;
2775 ti_new->latest_engine = node_first->latest_engine;
2777 setString(&ti_new->class_desc, "level sets");
2779 ti_new->node_group = node_first;
2780 ti_new->level_group = TRUE;
2782 ti_new2 = createParentTreeInfoNode(ti_new);
2784 setString(&ti_new2->name, ".. (main menu)");
2785 setString(&ti_new2->name_sorting, ti_new2->name);
2791 // ----------------------------------------------------------------------------
2792 // functions for handling level and custom artwork info cache
2793 // ----------------------------------------------------------------------------
2795 static void LoadArtworkInfoCache(void)
2797 InitCacheDirectory();
2799 if (artworkinfo_cache_old == NULL)
2801 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2803 // try to load artwork info hash from already existing cache file
2804 artworkinfo_cache_old = loadSetupFileHash(filename);
2806 // if no artwork info cache file was found, start with empty hash
2807 if (artworkinfo_cache_old == NULL)
2808 artworkinfo_cache_old = newSetupFileHash();
2813 if (artworkinfo_cache_new == NULL)
2814 artworkinfo_cache_new = newSetupFileHash();
2817 static void SaveArtworkInfoCache(void)
2819 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2821 InitCacheDirectory();
2823 saveSetupFileHash(artworkinfo_cache_new, filename);
2828 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2830 static char *prefix = NULL;
2832 checked_free(prefix);
2834 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2839 // (identical to above function, but separate string buffer needed -- nasty)
2840 static char *getCacheToken(char *prefix, char *suffix)
2842 static char *token = NULL;
2844 checked_free(token);
2846 token = getStringCat2WithSeparator(prefix, suffix, ".");
2851 static char *getFileTimestampString(char *filename)
2853 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2856 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2858 struct stat file_status;
2860 if (timestamp_string == NULL)
2863 if (stat(filename, &file_status) != 0) // cannot stat file
2866 return (file_status.st_mtime != atoi(timestamp_string));
2869 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2871 char *identifier = level_node->subdir;
2872 char *type_string = ARTWORK_DIRECTORY(type);
2873 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2874 char *token_main = getCacheToken(token_prefix, "CACHED");
2875 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2876 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2877 TreeInfo *artwork_info = NULL;
2879 if (!use_artworkinfo_cache)
2886 artwork_info = newTreeInfo();
2887 setTreeInfoToDefaults(artwork_info, type);
2889 // set all structure fields according to the token/value pairs
2890 ldi = *artwork_info;
2891 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2893 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2894 char *value = getHashEntry(artworkinfo_cache_old, token);
2896 // if defined, use value from cache, else keep default value
2898 setSetupInfo(artworkinfo_tokens, i, value);
2901 *artwork_info = ldi;
2903 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2904 LEVELINFO_FILENAME);
2905 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2906 ARTWORKINFO_FILENAME(type));
2908 // check if corresponding "levelinfo.conf" file has changed
2909 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2910 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2912 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2915 // check if corresponding "<artworkinfo>.conf" file has changed
2916 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2917 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2919 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2922 checked_free(filename_levelinfo);
2923 checked_free(filename_artworkinfo);
2926 if (!cached && artwork_info != NULL)
2928 freeTreeInfo(artwork_info);
2933 return artwork_info;
2936 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2937 LevelDirTree *level_node, int type)
2939 char *identifier = level_node->subdir;
2940 char *type_string = ARTWORK_DIRECTORY(type);
2941 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2942 char *token_main = getCacheToken(token_prefix, "CACHED");
2943 boolean set_cache_timestamps = TRUE;
2946 setHashEntry(artworkinfo_cache_new, token_main, "true");
2948 if (set_cache_timestamps)
2950 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2951 LEVELINFO_FILENAME);
2952 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2953 ARTWORKINFO_FILENAME(type));
2954 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2955 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2957 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2958 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2960 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2961 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2963 checked_free(filename_levelinfo);
2964 checked_free(filename_artworkinfo);
2965 checked_free(timestamp_levelinfo);
2966 checked_free(timestamp_artworkinfo);
2969 ldi = *artwork_info;
2970 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2972 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2973 char *value = getSetupValue(artworkinfo_tokens[i].type,
2974 artworkinfo_tokens[i].value);
2976 setHashEntry(artworkinfo_cache_new, token, value);
2981 // ----------------------------------------------------------------------------
2982 // functions for loading level info and custom artwork info
2983 // ----------------------------------------------------------------------------
2985 int GetZipFileTreeType(char *zip_filename)
2987 static char *top_dir_path = NULL;
2988 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
2989 static char *conf_basename[NUM_BASE_TREE_TYPES] =
2991 GRAPHICSINFO_FILENAME,
2992 SOUNDSINFO_FILENAME,
2998 checked_free(top_dir_path);
2999 top_dir_path = NULL;
3001 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3003 checked_free(top_dir_conf_filename[j]);
3004 top_dir_conf_filename[j] = NULL;
3007 char **zip_entries = zip_list(zip_filename);
3009 // check if zip file successfully opened
3010 if (zip_entries == NULL || zip_entries[0] == NULL)
3011 return TREE_TYPE_UNDEFINED;
3013 // first zip file entry is expected to be top level directory
3014 char *top_dir = zip_entries[0];
3016 // check if valid top level directory found in zip file
3017 if (!strSuffix(top_dir, "/"))
3018 return TREE_TYPE_UNDEFINED;
3020 // get filenames of valid configuration files in top level directory
3021 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3022 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3024 int tree_type = TREE_TYPE_UNDEFINED;
3027 while (zip_entries[e] != NULL)
3029 // check if every zip file entry is below top level directory
3030 if (!strPrefix(zip_entries[e], top_dir))
3031 return TREE_TYPE_UNDEFINED;
3033 // check if this zip file entry is a valid configuration filename
3034 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3036 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3038 // only exactly one valid configuration file allowed
3039 if (tree_type != TREE_TYPE_UNDEFINED)
3040 return TREE_TYPE_UNDEFINED;
3052 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3055 static char *top_dir_path = NULL;
3056 static char *top_dir_conf_filename = NULL;
3058 checked_free(top_dir_path);
3059 checked_free(top_dir_conf_filename);
3061 top_dir_path = NULL;
3062 top_dir_conf_filename = NULL;
3064 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3065 ARTWORKINFO_FILENAME(tree_type));
3067 // check if valid configuration filename determined
3068 if (conf_basename == NULL || strEqual(conf_basename, ""))
3071 char **zip_entries = zip_list(zip_filename);
3073 // check if zip file successfully opened
3074 if (zip_entries == NULL || zip_entries[0] == NULL)
3077 // first zip file entry is expected to be top level directory
3078 char *top_dir = zip_entries[0];
3080 // check if valid top level directory found in zip file
3081 if (!strSuffix(top_dir, "/"))
3084 // get path of extracted top level directory
3085 top_dir_path = getPath2(directory, top_dir);
3087 // remove trailing directory separator from top level directory path
3088 // (required to be able to check for file and directory in next step)
3089 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3091 // check if zip file's top level directory already exists in target directory
3092 if (fileExists(top_dir_path)) // (checks for file and directory)
3095 // get filename of configuration file in top level directory
3096 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3098 boolean found_top_dir_conf_filename = FALSE;
3101 while (zip_entries[i] != NULL)
3103 // check if every zip file entry is below top level directory
3104 if (!strPrefix(zip_entries[i], top_dir))
3107 // check if this zip file entry is the configuration filename
3108 if (strEqual(zip_entries[i], top_dir_conf_filename))
3109 found_top_dir_conf_filename = TRUE;
3114 // check if valid configuration filename was found in zip file
3115 if (!found_top_dir_conf_filename)
3121 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3124 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3127 if (!zip_file_valid)
3129 Error(ERR_WARN, "zip file '%s' rejected!", zip_filename);
3134 char **zip_entries = zip_extract(zip_filename, directory);
3136 if (zip_entries == NULL)
3138 Error(ERR_WARN, "zip file '%s' could not be extracted!", zip_filename);
3143 Error(ERR_INFO, "zip file '%s' successfully extracted!", zip_filename);
3145 // first zip file entry contains top level directory
3146 char *top_dir = zip_entries[0];
3148 // remove trailing directory separator from top level directory
3149 top_dir[strlen(top_dir) - 1] = '\0';
3154 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3157 DirectoryEntry *dir_entry;
3159 if ((dir = openDirectory(directory)) == NULL)
3161 // display error if directory is main "options.graphics_directory" etc.
3162 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3163 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3164 Error(ERR_WARN, "cannot read directory '%s'", directory);
3169 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3171 // skip non-zip files (and also directories with zip extension)
3172 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3175 char *zip_filename = getPath2(directory, dir_entry->basename);
3176 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3177 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3179 // check if zip file hasn't already been extracted or rejected
3180 if (!fileExists(zip_filename_extracted) &&
3181 !fileExists(zip_filename_rejected))
3183 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3185 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3186 zip_filename_rejected);
3189 // create empty file to mark zip file as extracted or rejected
3190 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3191 fclose(marker_file);
3194 free(zip_filename_extracted);
3195 free(zip_filename_rejected);
3199 closeDirectory(dir);
3202 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3203 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3205 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3206 TreeInfo *node_parent,
3207 char *level_directory,
3208 char *directory_name)
3210 char *directory_path = getPath2(level_directory, directory_name);
3211 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3212 SetupFileHash *setup_file_hash;
3213 LevelDirTree *leveldir_new = NULL;
3216 // unless debugging, silently ignore directories without "levelinfo.conf"
3217 if (!options.debug && !fileExists(filename))
3219 free(directory_path);
3225 setup_file_hash = loadSetupFileHash(filename);
3227 if (setup_file_hash == NULL)
3229 #if DEBUG_NO_CONFIG_FILE
3230 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3233 free(directory_path);
3239 leveldir_new = newTreeInfo();
3242 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3244 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3246 leveldir_new->subdir = getStringCopy(directory_name);
3248 // set all structure fields according to the token/value pairs
3249 ldi = *leveldir_new;
3250 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3251 setSetupInfo(levelinfo_tokens, i,
3252 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3253 *leveldir_new = ldi;
3255 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3256 setString(&leveldir_new->name, leveldir_new->subdir);
3258 if (leveldir_new->identifier == NULL)
3259 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3261 if (leveldir_new->name_sorting == NULL)
3262 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3264 if (node_parent == NULL) // top level group
3266 leveldir_new->basepath = getStringCopy(level_directory);
3267 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3269 else // sub level group
3271 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3272 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3275 leveldir_new->last_level =
3276 leveldir_new->first_level + leveldir_new->levels - 1;
3278 leveldir_new->in_user_dir =
3279 (!strEqual(leveldir_new->basepath, options.level_directory));
3281 // adjust some settings if user's private level directory was detected
3282 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3283 leveldir_new->in_user_dir &&
3284 (strEqual(leveldir_new->subdir, getLoginName()) ||
3285 strEqual(leveldir_new->name, getLoginName()) ||
3286 strEqual(leveldir_new->author, getRealName())))
3288 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3289 leveldir_new->readonly = FALSE;
3292 leveldir_new->user_defined =
3293 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3295 leveldir_new->color = LEVELCOLOR(leveldir_new);
3297 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3299 leveldir_new->handicap_level = // set handicap to default value
3300 (leveldir_new->user_defined || !leveldir_new->handicap ?
3301 leveldir_new->last_level : leveldir_new->first_level);
3303 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3305 pushTreeInfo(node_first, leveldir_new);
3307 freeSetupFileHash(setup_file_hash);
3309 if (leveldir_new->level_group)
3311 // create node to link back to current level directory
3312 createParentTreeInfoNode(leveldir_new);
3314 // recursively step into sub-directory and look for more level series
3315 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3316 leveldir_new, directory_path);
3319 free(directory_path);
3325 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3326 TreeInfo *node_parent,
3327 char *level_directory)
3329 // ---------- 1st stage: process any level set zip files ----------
3331 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3333 // ---------- 2nd stage: check for level set directories ----------
3336 DirectoryEntry *dir_entry;
3337 boolean valid_entry_found = FALSE;
3339 if ((dir = openDirectory(level_directory)) == NULL)
3341 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3346 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3348 char *directory_name = dir_entry->basename;
3349 char *directory_path = getPath2(level_directory, directory_name);
3351 // skip entries for current and parent directory
3352 if (strEqual(directory_name, ".") ||
3353 strEqual(directory_name, ".."))
3355 free(directory_path);
3360 // find out if directory entry is itself a directory
3361 if (!dir_entry->is_directory) // not a directory
3363 free(directory_path);
3368 free(directory_path);
3370 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3371 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3372 strEqual(directory_name, MUSIC_DIRECTORY))
3375 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3380 closeDirectory(dir);
3382 // special case: top level directory may directly contain "levelinfo.conf"
3383 if (node_parent == NULL && !valid_entry_found)
3385 // check if this directory directly contains a file "levelinfo.conf"
3386 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3387 level_directory, ".");
3390 if (!valid_entry_found)
3391 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3395 boolean AdjustGraphicsForEMC(void)
3397 boolean settings_changed = FALSE;
3399 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3400 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3402 return settings_changed;
3405 void LoadLevelInfo(void)
3407 InitUserLevelDirectory(getLoginName());
3409 DrawInitText("Loading level series", 120, FC_GREEN);
3411 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3412 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3414 leveldir_first = createTopTreeInfoNode(leveldir_first);
3416 /* after loading all level set information, clone the level directory tree
3417 and remove all level sets without levels (these may still contain artwork
3418 to be offered in the setup menu as "custom artwork", and are therefore
3419 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3420 leveldir_first_all = leveldir_first;
3421 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3423 AdjustGraphicsForEMC();
3425 // before sorting, the first entries will be from the user directory
3426 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3428 if (leveldir_first == NULL)
3429 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3431 sortTreeInfo(&leveldir_first);
3433 #if ENABLE_UNUSED_CODE
3434 dumpTreeInfo(leveldir_first, 0);
3438 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3439 TreeInfo *node_parent,
3440 char *base_directory,
3441 char *directory_name, int type)
3443 char *directory_path = getPath2(base_directory, directory_name);
3444 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3445 SetupFileHash *setup_file_hash = NULL;
3446 TreeInfo *artwork_new = NULL;
3449 if (fileExists(filename))
3450 setup_file_hash = loadSetupFileHash(filename);
3452 if (setup_file_hash == NULL) // no config file -- look for artwork files
3455 DirectoryEntry *dir_entry;
3456 boolean valid_file_found = FALSE;
3458 if ((dir = openDirectory(directory_path)) != NULL)
3460 while ((dir_entry = readDirectory(dir)) != NULL)
3462 if (FileIsArtworkType(dir_entry->filename, type))
3464 valid_file_found = TRUE;
3470 closeDirectory(dir);
3473 if (!valid_file_found)
3475 #if DEBUG_NO_CONFIG_FILE
3476 if (!strEqual(directory_name, "."))
3477 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3480 free(directory_path);
3487 artwork_new = newTreeInfo();
3490 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3492 setTreeInfoToDefaults(artwork_new, type);
3494 artwork_new->subdir = getStringCopy(directory_name);
3496 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3498 // set all structure fields according to the token/value pairs
3500 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3501 setSetupInfo(levelinfo_tokens, i,
3502 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3505 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3506 setString(&artwork_new->name, artwork_new->subdir);
3508 if (artwork_new->identifier == NULL)
3509 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3511 if (artwork_new->name_sorting == NULL)
3512 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3515 if (node_parent == NULL) // top level group
3517 artwork_new->basepath = getStringCopy(base_directory);
3518 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3520 else // sub level group
3522 artwork_new->basepath = getStringCopy(node_parent->basepath);
3523 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3526 artwork_new->in_user_dir =
3527 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3529 // (may use ".sort_priority" from "setup_file_hash" above)
3530 artwork_new->color = ARTWORKCOLOR(artwork_new);
3532 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3534 if (setup_file_hash == NULL) // (after determining ".user_defined")
3536 if (strEqual(artwork_new->subdir, "."))
3538 if (artwork_new->user_defined)
3540 setString(&artwork_new->identifier, "private");
3541 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3545 setString(&artwork_new->identifier, "classic");
3546 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3549 // set to new values after changing ".sort_priority"
3550 artwork_new->color = ARTWORKCOLOR(artwork_new);
3552 setString(&artwork_new->class_desc,
3553 getLevelClassDescription(artwork_new));
3557 setString(&artwork_new->identifier, artwork_new->subdir);
3560 setString(&artwork_new->name, artwork_new->identifier);
3561 setString(&artwork_new->name_sorting, artwork_new->name);
3564 pushTreeInfo(node_first, artwork_new);
3566 freeSetupFileHash(setup_file_hash);
3568 free(directory_path);
3574 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3575 TreeInfo *node_parent,
3576 char *base_directory, int type)
3578 // ---------- 1st stage: process any artwork set zip files ----------
3580 ProcessZipFilesInDirectory(base_directory, type);
3582 // ---------- 2nd stage: check for artwork set directories ----------
3585 DirectoryEntry *dir_entry;
3586 boolean valid_entry_found = FALSE;
3588 if ((dir = openDirectory(base_directory)) == NULL)
3590 // display error if directory is main "options.graphics_directory" etc.
3591 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3592 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3597 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3599 char *directory_name = dir_entry->basename;
3600 char *directory_path = getPath2(base_directory, directory_name);
3602 // skip directory entries for current and parent directory
3603 if (strEqual(directory_name, ".") ||
3604 strEqual(directory_name, ".."))
3606 free(directory_path);
3611 // skip directory entries which are not a directory
3612 if (!dir_entry->is_directory) // not a directory
3614 free(directory_path);
3619 free(directory_path);
3621 // check if this directory contains artwork with or without config file
3622 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3624 directory_name, type);
3627 closeDirectory(dir);
3629 // check if this directory directly contains artwork itself
3630 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3631 base_directory, ".",
3633 if (!valid_entry_found)
3634 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3638 static TreeInfo *getDummyArtworkInfo(int type)
3640 // this is only needed when there is completely no artwork available
3641 TreeInfo *artwork_new = newTreeInfo();
3643 setTreeInfoToDefaults(artwork_new, type);
3645 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3646 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3647 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3649 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3650 setString(&artwork_new->name, UNDEFINED_FILENAME);
3651 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3656 void LoadArtworkInfo(void)
3658 LoadArtworkInfoCache();
3660 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3662 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3663 options.graphics_directory,
3664 TREE_TYPE_GRAPHICS_DIR);
3665 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3666 getUserGraphicsDir(),
3667 TREE_TYPE_GRAPHICS_DIR);
3669 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3670 options.sounds_directory,
3671 TREE_TYPE_SOUNDS_DIR);
3672 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3674 TREE_TYPE_SOUNDS_DIR);
3676 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3677 options.music_directory,
3678 TREE_TYPE_MUSIC_DIR);
3679 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3681 TREE_TYPE_MUSIC_DIR);
3683 if (artwork.gfx_first == NULL)
3684 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3685 if (artwork.snd_first == NULL)
3686 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3687 if (artwork.mus_first == NULL)
3688 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3690 // before sorting, the first entries will be from the user directory
3691 artwork.gfx_current =
3692 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3693 if (artwork.gfx_current == NULL)
3694 artwork.gfx_current =
3695 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3696 if (artwork.gfx_current == NULL)
3697 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3699 artwork.snd_current =
3700 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3701 if (artwork.snd_current == NULL)
3702 artwork.snd_current =
3703 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3704 if (artwork.snd_current == NULL)
3705 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3707 artwork.mus_current =
3708 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3709 if (artwork.mus_current == NULL)
3710 artwork.mus_current =
3711 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3712 if (artwork.mus_current == NULL)
3713 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3715 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3716 artwork.snd_current_identifier = artwork.snd_current->identifier;
3717 artwork.mus_current_identifier = artwork.mus_current->identifier;
3719 #if ENABLE_UNUSED_CODE
3720 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3721 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3722 printf("music set == %s\n\n", artwork.mus_current_identifier);
3725 sortTreeInfo(&artwork.gfx_first);
3726 sortTreeInfo(&artwork.snd_first);
3727 sortTreeInfo(&artwork.mus_first);
3729 #if ENABLE_UNUSED_CODE
3730 dumpTreeInfo(artwork.gfx_first, 0);
3731 dumpTreeInfo(artwork.snd_first, 0);
3732 dumpTreeInfo(artwork.mus_first, 0);
3736 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3737 LevelDirTree *level_node)
3739 int type = (*artwork_node)->type;
3741 // recursively check all level directories for artwork sub-directories
3745 // check all tree entries for artwork, but skip parent link entries
3746 if (!level_node->parent_link)
3748 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3749 boolean cached = (artwork_new != NULL);
3753 pushTreeInfo(artwork_node, artwork_new);
3757 TreeInfo *topnode_last = *artwork_node;
3758 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3759 ARTWORK_DIRECTORY(type));
3761 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3763 if (topnode_last != *artwork_node) // check for newly added node
3765 artwork_new = *artwork_node;
3767 setString(&artwork_new->identifier, level_node->subdir);
3768 setString(&artwork_new->name, level_node->name);
3769 setString(&artwork_new->name_sorting, level_node->name_sorting);
3771 artwork_new->sort_priority = level_node->sort_priority;
3772 artwork_new->color = LEVELCOLOR(artwork_new);
3778 // insert artwork info (from old cache or filesystem) into new cache
3779 if (artwork_new != NULL)
3780 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3783 DrawInitText(level_node->name, 150, FC_YELLOW);
3785 if (level_node->node_group != NULL)
3786 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3788 level_node = level_node->next;
3792 void LoadLevelArtworkInfo(void)
3794 print_timestamp_init("LoadLevelArtworkInfo");
3796 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3798 print_timestamp_time("DrawTimeText");
3800 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3801 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3802 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3803 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3804 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3805 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3807 SaveArtworkInfoCache();
3809 print_timestamp_time("SaveArtworkInfoCache");
3811 // needed for reloading level artwork not known at ealier stage
3813 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3815 artwork.gfx_current =
3816 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3817 if (artwork.gfx_current == NULL)
3818 artwork.gfx_current =
3819 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3820 if (artwork.gfx_current == NULL)
3821 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3824 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3826 artwork.snd_current =
3827 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3828 if (artwork.snd_current == NULL)
3829 artwork.snd_current =
3830 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3831 if (artwork.snd_current == NULL)
3832 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3835 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3837 artwork.mus_current =
3838 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3839 if (artwork.mus_current == NULL)
3840 artwork.mus_current =
3841 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3842 if (artwork.mus_current == NULL)
3843 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3846 print_timestamp_time("getTreeInfoFromIdentifier");
3848 sortTreeInfo(&artwork.gfx_first);
3849 sortTreeInfo(&artwork.snd_first);
3850 sortTreeInfo(&artwork.mus_first);
3852 print_timestamp_time("sortTreeInfo");
3854 #if ENABLE_UNUSED_CODE
3855 dumpTreeInfo(artwork.gfx_first, 0);
3856 dumpTreeInfo(artwork.snd_first, 0);
3857 dumpTreeInfo(artwork.mus_first, 0);
3860 print_timestamp_done("LoadLevelArtworkInfo");
3863 static boolean AddUserTreeSetToTreeInfoExt(char *tree_subdir_new, int type)
3865 TreeInfo **tree_node_first, *tree_node_old, *tree_node_new;
3866 char *tree_user_dir = TREE_USERDIR(type);
3868 if (tree_user_dir == NULL) // should not happen
3871 // get first node of level or artwork tree
3872 tree_node_first = TREE_FIRST_NODE_PTR(type);
3874 if (tree_node_first == NULL) // should not happen
3877 if (type == TREE_TYPE_LEVEL_DIR)
3879 // get level info tree node of personal user level set
3880 tree_node_old = getTreeInfoFromIdentifier(*tree_node_first, getLoginName());
3884 // get artwork info tree node of first artwork set
3885 tree_node_old = *tree_node_first;
3888 if (tree_node_old == NULL) // should not happen
3891 int draw_deactivation_mask = GetDrawDeactivationMask();
3893 // override draw deactivation mask (temporarily disable drawing)
3894 SetDrawDeactivationMask(REDRAW_ALL);
3896 if (type == TREE_TYPE_LEVEL_DIR)
3898 // load new level set config and add it next to first user level set
3899 LoadLevelInfoFromLevelConf(&tree_node_old->next, NULL,
3900 tree_user_dir, tree_subdir_new);
3904 // load new artwork set config and add it next to first artwork set
3905 LoadArtworkInfoFromArtworkConf(&tree_node_old->next, NULL,
3906 tree_user_dir, tree_subdir_new, type);
3909 // set draw deactivation mask to previous value
3910 SetDrawDeactivationMask(draw_deactivation_mask);
3912 // get tree info tree node of newly added tree set
3913 tree_node_new = getTreeInfoFromIdentifier(*tree_node_first, tree_subdir_new);
3915 if (tree_node_new == NULL) // should not happen
3918 // correct top link and parent node link of newly created tree node
3919 tree_node_new->node_top = tree_node_old->node_top;
3920 tree_node_new->node_parent = tree_node_old->node_parent;
3922 // sort tree info tree to adjust position of newly added tree set
3923 sortTreeInfo(tree_node_first);
3928 void AddUserTreeSetToTreeInfo(char *tree_subdir_new, int type)
3930 if (!AddUserTreeSetToTreeInfoExt(tree_subdir_new, type))
3931 Error(ERR_EXIT, "internal tree set structure corrupted -- aborting");
3934 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3936 AddUserTreeSetToTreeInfo(level_subdir_new, TREE_TYPE_LEVEL_DIR);
3939 char *getArtworkIdentifierForUserLevelSet(int type)
3941 char *classic_artwork_set = getClassicArtworkSet(type);
3943 // check for custom artwork configured in "levelinfo.conf"
3944 char *leveldir_artwork_set =
3945 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3946 boolean has_leveldir_artwork_set =
3947 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3948 classic_artwork_set));
3950 // check for custom artwork in sub-directory "graphics" etc.
3951 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3952 char *leveldir_identifier = leveldir_current->identifier;
3953 boolean has_artwork_subdir =
3954 (getTreeInfoFromIdentifier(artwork_first_node,
3955 leveldir_identifier) != NULL);
3957 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3958 has_artwork_subdir ? leveldir_identifier :
3959 classic_artwork_set);
3962 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3964 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3965 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3967 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3970 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
3972 char *graphics_set =
3973 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3975 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3977 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3979 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3980 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3981 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3984 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3985 char *level_author, int num_levels)
3987 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3988 char *filename_tmp = getStringCat2(filename, ".tmp");
3990 FILE *file_tmp = NULL;
3991 char line[MAX_LINE_LEN];
3992 boolean success = FALSE;
3993 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3995 // update values in level directory tree
3997 if (level_name != NULL)
3998 setString(&leveldir->name, level_name);
4000 if (level_author != NULL)
4001 setString(&leveldir->author, level_author);
4003 if (num_levels != -1)
4004 leveldir->levels = num_levels;
4006 // update values that depend on other values
4008 setString(&leveldir->name_sorting, leveldir->name);
4010 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4012 // sort order of level sets may have changed
4013 sortTreeInfo(&leveldir_first);
4015 if ((file = fopen(filename, MODE_READ)) &&
4016 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4018 while (fgets(line, MAX_LINE_LEN, file))
4020 if (strPrefix(line, "name:") && level_name != NULL)
4021 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4022 else if (strPrefix(line, "author:") && level_author != NULL)
4023 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4024 else if (strPrefix(line, "levels:") && num_levels != -1)
4025 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4027 fputs(line, file_tmp);
4040 success = (rename(filename_tmp, filename) == 0);
4048 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4049 char *level_author, int num_levels,
4050 boolean use_artwork_set)
4052 LevelDirTree *level_info;
4057 // create user level sub-directory, if needed
4058 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4060 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4062 if (!(file = fopen(filename, MODE_WRITE)))
4064 Error(ERR_WARN, "cannot write level info file '%s'", filename);
4070 level_info = newTreeInfo();
4072 // always start with reliable default values
4073 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4075 setString(&level_info->name, level_name);
4076 setString(&level_info->author, level_author);
4077 level_info->levels = num_levels;
4078 level_info->first_level = 1;
4079 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4080 level_info->readonly = FALSE;
4082 if (use_artwork_set)
4084 level_info->graphics_set =
4085 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4086 level_info->sounds_set =
4087 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4088 level_info->music_set =
4089 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4092 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4094 fprintFileHeader(file, LEVELINFO_FILENAME);
4097 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4099 if (i == LEVELINFO_TOKEN_NAME ||
4100 i == LEVELINFO_TOKEN_AUTHOR ||
4101 i == LEVELINFO_TOKEN_LEVELS ||
4102 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4103 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4104 i == LEVELINFO_TOKEN_READONLY ||
4105 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4106 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4107 i == LEVELINFO_TOKEN_MUSIC_SET)))
4108 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4110 // just to make things nicer :)
4111 if (i == LEVELINFO_TOKEN_AUTHOR ||
4112 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4113 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4114 fprintf(file, "\n");
4117 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4121 SetFilePermissions(filename, PERMS_PRIVATE);
4123 freeTreeInfo(level_info);
4129 static void SaveUserLevelInfo(void)
4131 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4134 char *getSetupValue(int type, void *value)
4136 static char value_string[MAX_LINE_LEN];
4144 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4148 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4152 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4153 *(int *)value == FALSE ? "off" : "on"));
4157 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4160 case TYPE_YES_NO_AUTO:
4161 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4162 *(int *)value == FALSE ? "no" : "yes"));
4166 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4170 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4174 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4178 sprintf(value_string, "%d", *(int *)value);
4182 if (*(char **)value == NULL)
4185 strcpy(value_string, *(char **)value);
4189 sprintf(value_string, "player_%d", *(int *)value + 1);
4193 value_string[0] = '\0';
4197 if (type & TYPE_GHOSTED)
4198 strcpy(value_string, "n/a");
4200 return value_string;
4203 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4207 static char token_string[MAX_LINE_LEN];
4208 int token_type = token_info[token_nr].type;
4209 void *setup_value = token_info[token_nr].value;
4210 char *token_text = token_info[token_nr].text;
4211 char *value_string = getSetupValue(token_type, setup_value);
4213 // build complete token string
4214 sprintf(token_string, "%s%s", prefix, token_text);
4216 // build setup entry line
4217 line = getFormattedSetupEntry(token_string, value_string);
4219 if (token_type == TYPE_KEY_X11)
4221 Key key = *(Key *)setup_value;
4222 char *keyname = getKeyNameFromKey(key);
4224 // add comment, if useful
4225 if (!strEqual(keyname, "(undefined)") &&
4226 !strEqual(keyname, "(unknown)"))
4228 // add at least one whitespace
4230 for (i = strlen(line); i < token_comment_position; i++)
4234 strcat(line, keyname);
4241 void LoadLevelSetup_LastSeries(void)
4243 // --------------------------------------------------------------------------
4244 // ~/.<program>/levelsetup.conf
4245 // --------------------------------------------------------------------------
4247 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4248 SetupFileHash *level_setup_hash = NULL;
4250 // always start with reliable default values
4251 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4253 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4255 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4257 if (leveldir_current == NULL)
4258 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4261 if ((level_setup_hash = loadSetupFileHash(filename)))
4263 char *last_level_series =
4264 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4266 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4268 if (leveldir_current == NULL)
4269 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4271 freeSetupFileHash(level_setup_hash);
4275 Error(ERR_DEBUG, "using default setup values");
4281 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4283 // --------------------------------------------------------------------------
4284 // ~/.<program>/levelsetup.conf
4285 // --------------------------------------------------------------------------
4287 // check if the current level directory structure is available at this point
4288 if (leveldir_current == NULL)
4291 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4292 char *level_subdir = leveldir_current->subdir;
4295 InitUserDataDirectory();
4297 if (!(file = fopen(filename, MODE_WRITE)))
4299 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4306 fprintFileHeader(file, LEVELSETUP_FILENAME);
4308 if (deactivate_last_level_series)
4309 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4311 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4316 SetFilePermissions(filename, PERMS_PRIVATE);
4321 void SaveLevelSetup_LastSeries(void)
4323 SaveLevelSetup_LastSeries_Ext(FALSE);
4326 void SaveLevelSetup_LastSeries_Deactivate(void)
4328 SaveLevelSetup_LastSeries_Ext(TRUE);
4331 static void checkSeriesInfo(void)
4333 static char *level_directory = NULL;
4336 DirectoryEntry *dir_entry;
4339 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4341 level_directory = getPath2((leveldir_current->in_user_dir ?
4342 getUserLevelDir(NULL) :
4343 options.level_directory),
4344 leveldir_current->fullpath);
4346 if ((dir = openDirectory(level_directory)) == NULL)
4348 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4354 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4356 if (strlen(dir_entry->basename) > 4 &&
4357 dir_entry->basename[3] == '.' &&
4358 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4360 char levelnum_str[4];
4363 strncpy(levelnum_str, dir_entry->basename, 3);
4364 levelnum_str[3] = '\0';
4366 levelnum_value = atoi(levelnum_str);
4368 if (levelnum_value < leveldir_current->first_level)
4370 Error(ERR_WARN, "additional level %d found", levelnum_value);
4371 leveldir_current->first_level = levelnum_value;
4373 else if (levelnum_value > leveldir_current->last_level)
4375 Error(ERR_WARN, "additional level %d found", levelnum_value);
4376 leveldir_current->last_level = levelnum_value;
4382 closeDirectory(dir);
4385 void LoadLevelSetup_SeriesInfo(void)
4388 SetupFileHash *level_setup_hash = NULL;
4389 char *level_subdir = leveldir_current->subdir;
4392 // always start with reliable default values
4393 level_nr = leveldir_current->first_level;
4395 for (i = 0; i < MAX_LEVELS; i++)
4397 LevelStats_setPlayed(i, 0);
4398 LevelStats_setSolved(i, 0);
4403 // --------------------------------------------------------------------------
4404 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4405 // --------------------------------------------------------------------------
4407 level_subdir = leveldir_current->subdir;
4409 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4411 if ((level_setup_hash = loadSetupFileHash(filename)))
4415 // get last played level in this level set
4417 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4421 level_nr = atoi(token_value);
4423 if (level_nr < leveldir_current->first_level)
4424 level_nr = leveldir_current->first_level;
4425 if (level_nr > leveldir_current->last_level)
4426 level_nr = leveldir_current->last_level;
4429 // get handicap level in this level set
4431 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4435 int level_nr = atoi(token_value);
4437 if (level_nr < leveldir_current->first_level)
4438 level_nr = leveldir_current->first_level;
4439 if (level_nr > leveldir_current->last_level + 1)
4440 level_nr = leveldir_current->last_level;
4442 if (leveldir_current->user_defined || !leveldir_current->handicap)
4443 level_nr = leveldir_current->last_level;
4445 leveldir_current->handicap_level = level_nr;
4448 // get number of played and solved levels in this level set
4450 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4452 char *token = HASH_ITERATION_TOKEN(itr);
4453 char *value = HASH_ITERATION_VALUE(itr);
4455 if (strlen(token) == 3 &&
4456 token[0] >= '0' && token[0] <= '9' &&
4457 token[1] >= '0' && token[1] <= '9' &&
4458 token[2] >= '0' && token[2] <= '9')
4460 int level_nr = atoi(token);
4463 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4465 value = strchr(value, ' ');
4468 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4471 END_HASH_ITERATION(hash, itr)
4473 freeSetupFileHash(level_setup_hash);
4477 Error(ERR_DEBUG, "using default setup values");
4483 void SaveLevelSetup_SeriesInfo(void)
4486 char *level_subdir = leveldir_current->subdir;
4487 char *level_nr_str = int2str(level_nr, 0);
4488 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4492 // --------------------------------------------------------------------------
4493 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4494 // --------------------------------------------------------------------------
4496 InitLevelSetupDirectory(level_subdir);
4498 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4500 if (!(file = fopen(filename, MODE_WRITE)))
4502 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4507 fprintFileHeader(file, LEVELSETUP_FILENAME);
4509 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4511 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4512 handicap_level_str));
4514 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4517 if (LevelStats_getPlayed(i) > 0 ||
4518 LevelStats_getSolved(i) > 0)
4523 sprintf(token, "%03d", i);
4524 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4526 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4532 SetFilePermissions(filename, PERMS_PRIVATE);
4537 int LevelStats_getPlayed(int nr)
4539 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4542 int LevelStats_getSolved(int nr)
4544 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4547 void LevelStats_setPlayed(int nr, int value)
4549 if (nr >= 0 && nr < MAX_LEVELS)
4550 level_stats[nr].played = value;
4553 void LevelStats_setSolved(int nr, int value)
4555 if (nr >= 0 && nr < MAX_LEVELS)
4556 level_stats[nr].solved = value;
4559 void LevelStats_incPlayed(int nr)
4561 if (nr >= 0 && nr < MAX_LEVELS)
4562 level_stats[nr].played++;
4565 void LevelStats_incSolved(int nr)
4567 if (nr >= 0 && nr < MAX_LEVELS)
4568 level_stats[nr].solved++;