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>
33 #define ENABLE_UNUSED_CODE FALSE /* for currently unused functions */
35 #define NUM_LEVELCLASS_DESC 8
37 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
50 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
51 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
52 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
58 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
61 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
62 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
63 IS_LEVELCLASS_BD(n) ? 2 : \
64 IS_LEVELCLASS_EM(n) ? 3 : \
65 IS_LEVELCLASS_SP(n) ? 4 : \
66 IS_LEVELCLASS_DX(n) ? 5 : \
67 IS_LEVELCLASS_SB(n) ? 6 : \
68 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
69 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
72 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
73 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
74 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
75 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
78 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
79 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
80 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
81 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
84 #define TOKEN_VALUE_POSITION_SHORT 32
85 #define TOKEN_VALUE_POSITION_DEFAULT 40
86 #define TOKEN_COMMENT_POSITION_DEFAULT 60
88 #define MAX_COOKIE_LEN 256
91 static void setTreeInfoToDefaults(TreeInfo *, int);
92 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
93 static int compareTreeInfoEntries(const void *, const void *);
95 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
96 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
98 static SetupFileHash *artworkinfo_cache_old = NULL;
99 static SetupFileHash *artworkinfo_cache_new = NULL;
100 static boolean use_artworkinfo_cache = TRUE;
103 /* ------------------------------------------------------------------------- */
105 /* ------------------------------------------------------------------------- */
107 static char *getLevelClassDescription(TreeInfo *ti)
109 int position = ti->sort_priority / 100;
111 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
112 return levelclass_desc[position];
114 return "Unknown Level Class";
117 static char *getUserLevelDir(char *level_subdir)
119 static char *userlevel_dir = NULL;
120 char *data_dir = getUserGameDataDir();
121 char *userlevel_subdir = LEVELS_DIRECTORY;
123 checked_free(userlevel_dir);
125 if (level_subdir != NULL)
126 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
128 userlevel_dir = getPath2(data_dir, userlevel_subdir);
130 return userlevel_dir;
133 static char *getScoreDir(char *level_subdir)
135 static char *score_dir = NULL;
136 char *data_dir = getCommonDataDir();
137 char *score_subdir = SCORES_DIRECTORY;
139 checked_free(score_dir);
141 if (level_subdir != NULL)
142 score_dir = getPath3(data_dir, score_subdir, level_subdir);
144 score_dir = getPath2(data_dir, score_subdir);
149 static char *getLevelSetupDir(char *level_subdir)
151 static char *levelsetup_dir = NULL;
152 char *data_dir = getUserGameDataDir();
153 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
155 checked_free(levelsetup_dir);
157 if (level_subdir != NULL)
158 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
160 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
162 return levelsetup_dir;
165 static char *getCacheDir()
167 static char *cache_dir = NULL;
169 if (cache_dir == NULL)
170 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
175 static char *getLevelDirFromTreeInfo(TreeInfo *node)
177 static char *level_dir = NULL;
180 return options.level_directory;
182 checked_free(level_dir);
184 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
185 options.level_directory), node->fullpath);
190 char *getCurrentLevelDir()
192 return getLevelDirFromTreeInfo(leveldir_current);
195 static char *getTapeDir(char *level_subdir)
197 static char *tape_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *tape_subdir = TAPES_DIRECTORY;
201 checked_free(tape_dir);
203 if (level_subdir != NULL)
204 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
206 tape_dir = getPath2(data_dir, tape_subdir);
211 static char *getSolutionTapeDir()
213 static char *tape_dir = NULL;
214 char *data_dir = getCurrentLevelDir();
215 char *tape_subdir = TAPES_DIRECTORY;
217 checked_free(tape_dir);
219 tape_dir = getPath2(data_dir, tape_subdir);
224 static char *getDefaultGraphicsDir(char *graphics_subdir)
226 static char *graphics_dir = NULL;
228 if (graphics_subdir == NULL)
229 return options.graphics_directory;
231 checked_free(graphics_dir);
233 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
238 static char *getDefaultSoundsDir(char *sounds_subdir)
240 static char *sounds_dir = NULL;
242 if (sounds_subdir == NULL)
243 return options.sounds_directory;
245 checked_free(sounds_dir);
247 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
252 static char *getDefaultMusicDir(char *music_subdir)
254 static char *music_dir = NULL;
256 if (music_subdir == NULL)
257 return options.music_directory;
259 checked_free(music_dir);
261 music_dir = getPath2(options.music_directory, music_subdir);
266 static char *getClassicArtworkSet(int type)
268 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
269 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
270 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
273 static char *getClassicArtworkDir(int type)
275 return (type == TREE_TYPE_GRAPHICS_DIR ?
276 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
277 type == TREE_TYPE_SOUNDS_DIR ?
278 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
279 type == TREE_TYPE_MUSIC_DIR ?
280 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
283 static char *getUserGraphicsDir()
285 static char *usergraphics_dir = NULL;
287 if (usergraphics_dir == NULL)
288 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
290 return usergraphics_dir;
293 static char *getUserSoundsDir()
295 static char *usersounds_dir = NULL;
297 if (usersounds_dir == NULL)
298 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
300 return usersounds_dir;
303 static char *getUserMusicDir()
305 static char *usermusic_dir = NULL;
307 if (usermusic_dir == NULL)
308 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
310 return usermusic_dir;
313 static char *getSetupArtworkDir(TreeInfo *ti)
315 static char *artwork_dir = NULL;
320 checked_free(artwork_dir);
322 artwork_dir = getPath2(ti->basepath, ti->fullpath);
327 char *setLevelArtworkDir(TreeInfo *ti)
329 char **artwork_path_ptr, **artwork_set_ptr;
330 TreeInfo *level_artwork;
332 if (ti == NULL || leveldir_current == NULL)
335 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
336 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
338 checked_free(*artwork_path_ptr);
340 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
342 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
347 No (or non-existing) artwork configured in "levelinfo.conf". This would
348 normally result in using the artwork configured in the setup menu. But
349 if an artwork subdirectory exists (which might contain custom artwork
350 or an artwork configuration file), this level artwork must be treated
351 as relative to the default "classic" artwork, not to the artwork that
352 is currently configured in the setup menu.
354 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
355 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
356 the real "classic" artwork from the original R'n'D (like "gfx_classic").
359 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
361 checked_free(*artwork_set_ptr);
363 if (directoryExists(dir))
365 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
366 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
370 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
371 *artwork_set_ptr = NULL;
377 return *artwork_set_ptr;
380 inline static char *getLevelArtworkSet(int type)
382 if (leveldir_current == NULL)
385 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
388 inline static char *getLevelArtworkDir(int type)
390 if (leveldir_current == NULL)
391 return UNDEFINED_FILENAME;
393 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
396 char *getProgramConfigFilename(char *command_filename_ptr)
398 char *command_filename_1 = getStringCopy(command_filename_ptr);
400 // strip trailing executable suffix from command filename
401 if (strSuffix(command_filename_1, ".exe"))
402 command_filename_1[strlen(command_filename_1) - 4] = '\0';
404 char *command_basepath = getBasePath(command_filename_ptr);
405 char *command_basename = getBaseNameNoSuffix(command_filename_ptr);
406 char *command_filename_2 = getPath2(command_basepath, command_basename);
408 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
409 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
411 // 1st try: look for config file that exactly matches the binary filename
412 if (fileExists(config_filename_1))
413 return config_filename_1;
415 // 2nd try: return config filename that matches binary filename without suffix
416 return config_filename_2;
419 char *getTapeFilename(int nr)
421 static char *filename = NULL;
422 char basename[MAX_FILENAME_LEN];
424 checked_free(filename);
426 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
427 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
432 char *getSolutionTapeFilename(int nr)
434 static char *filename = NULL;
435 char basename[MAX_FILENAME_LEN];
437 checked_free(filename);
439 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
440 filename = getPath2(getSolutionTapeDir(), basename);
442 if (!fileExists(filename))
444 static char *filename_sln = NULL;
446 checked_free(filename_sln);
448 sprintf(basename, "%03d.sln", nr);
449 filename_sln = getPath2(getSolutionTapeDir(), basename);
451 if (fileExists(filename_sln))
458 char *getScoreFilename(int nr)
460 static char *filename = NULL;
461 char basename[MAX_FILENAME_LEN];
463 checked_free(filename);
465 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
466 filename = getPath2(getScoreDir(leveldir_current->subdir), basename);
471 char *getSetupFilename()
473 static char *filename = NULL;
475 checked_free(filename);
477 filename = getPath2(getSetupDir(), SETUP_FILENAME);
482 char *getDefaultSetupFilename()
484 return program.config_filename;
487 char *getEditorSetupFilename()
489 static char *filename = NULL;
491 checked_free(filename);
492 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
494 if (fileExists(filename))
497 checked_free(filename);
498 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
503 char *getHelpAnimFilename()
505 static char *filename = NULL;
507 checked_free(filename);
509 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
514 char *getHelpTextFilename()
516 static char *filename = NULL;
518 checked_free(filename);
520 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
525 char *getLevelSetInfoFilename()
527 static char *filename = NULL;
542 for (i = 0; basenames[i] != NULL; i++)
544 checked_free(filename);
545 filename = getPath2(getCurrentLevelDir(), basenames[i]);
547 if (fileExists(filename))
554 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
556 static char basename[32];
558 sprintf(basename, "%s_%d.txt",
559 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
564 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
566 static char *filename = NULL;
568 boolean skip_setup_artwork = FALSE;
570 checked_free(filename);
572 basename = getLevelSetTitleMessageBasename(nr, initial);
574 if (!gfx.override_level_graphics)
576 /* 1st try: look for special artwork in current level series directory */
577 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
578 if (fileExists(filename))
583 /* 2nd try: look for message file in current level set directory */
584 filename = getPath2(getCurrentLevelDir(), basename);
585 if (fileExists(filename))
590 /* check if there is special artwork configured in level series config */
591 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
593 /* 3rd try: look for special artwork configured in level series config */
594 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
595 if (fileExists(filename))
600 /* take missing artwork configured in level set config from default */
601 skip_setup_artwork = TRUE;
605 if (!skip_setup_artwork)
607 /* 4th try: look for special artwork in configured artwork directory */
608 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
609 if (fileExists(filename))
615 /* 5th try: look for default artwork in new default artwork directory */
616 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
617 if (fileExists(filename))
622 /* 6th try: look for default artwork in old default artwork directory */
623 filename = getPath2(options.graphics_directory, basename);
624 if (fileExists(filename))
627 return NULL; /* cannot find specified artwork file anywhere */
630 static char *getCorrectedArtworkBasename(char *basename)
635 char *getCustomImageFilename(char *basename)
637 static char *filename = NULL;
638 boolean skip_setup_artwork = FALSE;
640 checked_free(filename);
642 basename = getCorrectedArtworkBasename(basename);
644 if (!gfx.override_level_graphics)
646 /* 1st try: look for special artwork in current level series directory */
647 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
648 if (fileExists(filename))
653 /* check if there is special artwork configured in level series config */
654 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
656 /* 2nd try: look for special artwork configured in level series config */
657 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
658 if (fileExists(filename))
663 /* take missing artwork configured in level set config from default */
664 skip_setup_artwork = TRUE;
668 if (!skip_setup_artwork)
670 /* 3rd try: look for special artwork in configured artwork directory */
671 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
672 if (fileExists(filename))
678 /* 4th try: look for default artwork in new default artwork directory */
679 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
680 if (fileExists(filename))
685 /* 5th try: look for default artwork in old default artwork directory */
686 filename = getImg2(options.graphics_directory, basename);
687 if (fileExists(filename))
690 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
695 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
698 /* 6th try: look for fallback artwork in old default artwork directory */
699 /* (needed to prevent errors when trying to access unused artwork files) */
700 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
701 if (fileExists(filename))
705 return NULL; /* cannot find specified artwork file anywhere */
708 char *getCustomSoundFilename(char *basename)
710 static char *filename = NULL;
711 boolean skip_setup_artwork = FALSE;
713 checked_free(filename);
715 basename = getCorrectedArtworkBasename(basename);
717 if (!gfx.override_level_sounds)
719 /* 1st try: look for special artwork in current level series directory */
720 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
721 if (fileExists(filename))
726 /* check if there is special artwork configured in level series config */
727 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
729 /* 2nd try: look for special artwork configured in level series config */
730 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
731 if (fileExists(filename))
736 /* take missing artwork configured in level set config from default */
737 skip_setup_artwork = TRUE;
741 if (!skip_setup_artwork)
743 /* 3rd try: look for special artwork in configured artwork directory */
744 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
745 if (fileExists(filename))
751 /* 4th try: look for default artwork in new default artwork directory */
752 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
753 if (fileExists(filename))
758 /* 5th try: look for default artwork in old default artwork directory */
759 filename = getPath2(options.sounds_directory, basename);
760 if (fileExists(filename))
763 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
768 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
771 /* 6th try: look for fallback artwork in old default artwork directory */
772 /* (needed to prevent errors when trying to access unused artwork files) */
773 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
774 if (fileExists(filename))
778 return NULL; /* cannot find specified artwork file anywhere */
781 char *getCustomMusicFilename(char *basename)
783 static char *filename = NULL;
784 boolean skip_setup_artwork = FALSE;
786 checked_free(filename);
788 basename = getCorrectedArtworkBasename(basename);
790 if (!gfx.override_level_music)
792 /* 1st try: look for special artwork in current level series directory */
793 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
794 if (fileExists(filename))
799 /* check if there is special artwork configured in level series config */
800 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
802 /* 2nd try: look for special artwork configured in level series config */
803 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
804 if (fileExists(filename))
809 /* take missing artwork configured in level set config from default */
810 skip_setup_artwork = TRUE;
814 if (!skip_setup_artwork)
816 /* 3rd try: look for special artwork in configured artwork directory */
817 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
818 if (fileExists(filename))
824 /* 4th try: look for default artwork in new default artwork directory */
825 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
826 if (fileExists(filename))
831 /* 5th try: look for default artwork in old default artwork directory */
832 filename = getPath2(options.music_directory, basename);
833 if (fileExists(filename))
836 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
841 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
844 /* 6th try: look for fallback artwork in old default artwork directory */
845 /* (needed to prevent errors when trying to access unused artwork files) */
846 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
847 if (fileExists(filename))
851 return NULL; /* cannot find specified artwork file anywhere */
854 char *getCustomArtworkFilename(char *basename, int type)
856 if (type == ARTWORK_TYPE_GRAPHICS)
857 return getCustomImageFilename(basename);
858 else if (type == ARTWORK_TYPE_SOUNDS)
859 return getCustomSoundFilename(basename);
860 else if (type == ARTWORK_TYPE_MUSIC)
861 return getCustomMusicFilename(basename);
863 return UNDEFINED_FILENAME;
866 char *getCustomArtworkConfigFilename(int type)
868 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
871 char *getCustomArtworkLevelConfigFilename(int type)
873 static char *filename = NULL;
875 checked_free(filename);
877 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
882 char *getCustomMusicDirectory(void)
884 static char *directory = NULL;
885 boolean skip_setup_artwork = FALSE;
887 checked_free(directory);
889 if (!gfx.override_level_music)
891 /* 1st try: look for special artwork in current level series directory */
892 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
893 if (directoryExists(directory))
898 /* check if there is special artwork configured in level series config */
899 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
901 /* 2nd try: look for special artwork configured in level series config */
902 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
903 if (directoryExists(directory))
908 /* take missing artwork configured in level set config from default */
909 skip_setup_artwork = TRUE;
913 if (!skip_setup_artwork)
915 /* 3rd try: look for special artwork in configured artwork directory */
916 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
917 if (directoryExists(directory))
923 /* 4th try: look for default artwork in new default artwork directory */
924 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
925 if (directoryExists(directory))
930 /* 5th try: look for default artwork in old default artwork directory */
931 directory = getStringCopy(options.music_directory);
932 if (directoryExists(directory))
935 return NULL; /* cannot find specified artwork file anywhere */
938 void InitTapeDirectory(char *level_subdir)
940 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
941 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
942 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
945 void InitScoreDirectory(char *level_subdir)
947 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
948 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
949 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
952 static void SaveUserLevelInfo();
954 void InitUserLevelDirectory(char *level_subdir)
956 if (!directoryExists(getUserLevelDir(level_subdir)))
958 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
959 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
960 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
966 void InitLevelSetupDirectory(char *level_subdir)
968 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
969 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
970 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
973 void InitCacheDirectory()
975 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
976 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
980 /* ------------------------------------------------------------------------- */
981 /* some functions to handle lists of level and artwork directories */
982 /* ------------------------------------------------------------------------- */
984 TreeInfo *newTreeInfo()
986 return checked_calloc(sizeof(TreeInfo));
989 TreeInfo *newTreeInfo_setDefaults(int type)
991 TreeInfo *ti = newTreeInfo();
993 setTreeInfoToDefaults(ti, type);
998 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1000 node_new->next = *node_first;
1001 *node_first = node_new;
1004 int numTreeInfo(TreeInfo *node)
1017 boolean validLevelSeries(TreeInfo *node)
1019 return (node != NULL && !node->node_group && !node->parent_link);
1022 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1027 if (node->node_group) /* enter level group (step down into tree) */
1028 return getFirstValidTreeInfoEntry(node->node_group);
1029 else if (node->parent_link) /* skip start entry of level group */
1031 if (node->next) /* get first real level series entry */
1032 return getFirstValidTreeInfoEntry(node->next);
1033 else /* leave empty level group and go on */
1034 return getFirstValidTreeInfoEntry(node->node_parent->next);
1036 else /* this seems to be a regular level series */
1040 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1045 if (node->node_parent == NULL) /* top level group */
1046 return *node->node_top;
1047 else /* sub level group */
1048 return node->node_parent->node_group;
1051 int numTreeInfoInGroup(TreeInfo *node)
1053 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1056 int posTreeInfo(TreeInfo *node)
1058 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1063 if (node_cmp == node)
1067 node_cmp = node_cmp->next;
1073 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1075 TreeInfo *node_default = node;
1087 return node_default;
1090 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1092 if (identifier == NULL)
1097 if (node->node_group)
1099 TreeInfo *node_group;
1101 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1106 else if (!node->parent_link)
1108 if (strEqual(identifier, node->identifier))
1118 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1119 TreeInfo *node, boolean skip_sets_without_levels)
1126 if (!node->parent_link && !node->level_group &&
1127 skip_sets_without_levels && node->levels == 0)
1128 return cloneTreeNode(node_top, node_parent, node->next,
1129 skip_sets_without_levels);
1131 node_new = getTreeInfoCopy(node); /* copy complete node */
1133 node_new->node_top = node_top; /* correct top node link */
1134 node_new->node_parent = node_parent; /* correct parent node link */
1136 if (node->level_group)
1137 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1138 skip_sets_without_levels);
1140 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1141 skip_sets_without_levels);
1146 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1148 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1150 *ti_new = ti_cloned;
1153 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1155 boolean settings_changed = FALSE;
1159 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1160 !strEqual(node->graphics_set, node->graphics_set_ecs))
1162 setString(&node->graphics_set, node->graphics_set_ecs);
1163 settings_changed = TRUE;
1165 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1166 !strEqual(node->graphics_set, node->graphics_set_aga))
1168 setString(&node->graphics_set, node->graphics_set_aga);
1169 settings_changed = TRUE;
1172 if (node->node_group != NULL)
1173 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1178 return settings_changed;
1181 void dumpTreeInfo(TreeInfo *node, int depth)
1185 printf("Dumping TreeInfo:\n");
1189 for (i = 0; i < (depth + 1) * 3; i++)
1192 printf("'%s' / '%s'\n", node->identifier, node->name);
1195 // use for dumping artwork info tree
1196 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1197 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1200 if (node->node_group != NULL)
1201 dumpTreeInfo(node->node_group, depth + 1);
1207 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1208 int (*compare_function)(const void *,
1211 int num_nodes = numTreeInfo(*node_first);
1212 TreeInfo **sort_array;
1213 TreeInfo *node = *node_first;
1219 /* allocate array for sorting structure pointers */
1220 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1222 /* writing structure pointers to sorting array */
1223 while (i < num_nodes && node) /* double boundary check... */
1225 sort_array[i] = node;
1231 /* sorting the structure pointers in the sorting array */
1232 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1235 /* update the linkage of list elements with the sorted node array */
1236 for (i = 0; i < num_nodes - 1; i++)
1237 sort_array[i]->next = sort_array[i + 1];
1238 sort_array[num_nodes - 1]->next = NULL;
1240 /* update the linkage of the main list anchor pointer */
1241 *node_first = sort_array[0];
1245 /* now recursively sort the level group structures */
1249 if (node->node_group != NULL)
1250 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1256 void sortTreeInfo(TreeInfo **node_first)
1258 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1262 /* ========================================================================= */
1263 /* some stuff from "files.c" */
1264 /* ========================================================================= */
1266 #if defined(PLATFORM_WIN32)
1268 #define S_IRGRP S_IRUSR
1271 #define S_IROTH S_IRUSR
1274 #define S_IWGRP S_IWUSR
1277 #define S_IWOTH S_IWUSR
1280 #define S_IXGRP S_IXUSR
1283 #define S_IXOTH S_IXUSR
1286 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1291 #endif /* PLATFORM_WIN32 */
1293 /* file permissions for newly written files */
1294 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1295 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1296 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1298 #define MODE_W_PRIVATE (S_IWUSR)
1299 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
1300 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1302 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1303 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1305 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1306 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
1310 static char *dir = NULL;
1312 #if defined(PLATFORM_WIN32)
1315 dir = checked_malloc(MAX_PATH + 1);
1317 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1320 #elif defined(PLATFORM_UNIX)
1323 if ((dir = getenv("HOME")) == NULL)
1327 if ((pwd = getpwuid(getuid())) != NULL)
1328 dir = getStringCopy(pwd->pw_dir);
1340 char *getCommonDataDir(void)
1342 static char *common_data_dir = NULL;
1344 #if defined(PLATFORM_WIN32)
1345 if (common_data_dir == NULL)
1347 char *dir = checked_malloc(MAX_PATH + 1);
1349 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1350 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1351 common_data_dir = getPath2(dir, program.userdata_subdir);
1353 common_data_dir = options.rw_base_directory;
1356 if (common_data_dir == NULL)
1357 common_data_dir = options.rw_base_directory;
1360 return common_data_dir;
1363 char *getPersonalDataDir(void)
1365 static char *personal_data_dir = NULL;
1367 #if defined(PLATFORM_MACOSX)
1368 if (personal_data_dir == NULL)
1369 personal_data_dir = getPath2(getHomeDir(), "Documents");
1371 if (personal_data_dir == NULL)
1372 personal_data_dir = getHomeDir();
1375 return personal_data_dir;
1378 char *getUserGameDataDir(void)
1380 static char *user_game_data_dir = NULL;
1382 #if defined(PLATFORM_ANDROID)
1383 if (user_game_data_dir == NULL)
1384 user_game_data_dir = (char *)SDL_AndroidGetInternalStoragePath();
1386 if (user_game_data_dir == NULL)
1387 user_game_data_dir = getPath2(getPersonalDataDir(),
1388 program.userdata_subdir);
1391 return user_game_data_dir;
1396 return getUserGameDataDir();
1399 static mode_t posix_umask(mode_t mask)
1401 #if defined(PLATFORM_UNIX)
1408 static int posix_mkdir(const char *pathname, mode_t mode)
1410 #if defined(PLATFORM_WIN32)
1411 return mkdir(pathname);
1413 return mkdir(pathname, mode);
1417 static boolean posix_process_running_setgid()
1419 #if defined(PLATFORM_UNIX)
1420 return (getgid() != getegid());
1426 void createDirectory(char *dir, char *text, int permission_class)
1428 /* leave "other" permissions in umask untouched, but ensure group parts
1429 of USERDATA_DIR_MODE are not masked */
1430 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1431 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1432 mode_t last_umask = posix_umask(0);
1433 mode_t group_umask = ~(dir_mode & S_IRWXG);
1434 int running_setgid = posix_process_running_setgid();
1436 /* if we're setgid, protect files against "other" */
1437 /* else keep umask(0) to make the dir world-writable */
1440 posix_umask(last_umask & group_umask);
1442 dir_mode |= MODE_W_ALL;
1444 if (!directoryExists(dir))
1445 if (posix_mkdir(dir, dir_mode) != 0)
1446 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1447 text, dir, strerror(errno));
1449 if (permission_class == PERMS_PUBLIC && !running_setgid)
1450 chmod(dir, dir_mode);
1452 posix_umask(last_umask); /* restore previous umask */
1455 void InitUserDataDirectory()
1457 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1460 void SetFilePermissions(char *filename, int permission_class)
1462 int running_setgid = posix_process_running_setgid();
1463 int perms = (permission_class == PERMS_PRIVATE ?
1464 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1466 if (permission_class == PERMS_PUBLIC && !running_setgid)
1467 perms |= MODE_W_ALL;
1469 chmod(filename, perms);
1472 char *getCookie(char *file_type)
1474 static char cookie[MAX_COOKIE_LEN + 1];
1476 if (strlen(program.cookie_prefix) + 1 +
1477 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1478 return "[COOKIE ERROR]"; /* should never happen */
1480 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1481 program.cookie_prefix, file_type,
1482 program.version_major, program.version_minor);
1487 void fprintFileHeader(FILE *file, char *basename)
1489 char *prefix = "# ";
1492 fprintf_line_with_prefix(file, prefix, sep1, 77);
1493 fprintf(file, "%s%s\n", prefix, basename);
1494 fprintf_line_with_prefix(file, prefix, sep1, 77);
1495 fprintf(file, "\n");
1498 int getFileVersionFromCookieString(const char *cookie)
1500 const char *ptr_cookie1, *ptr_cookie2;
1501 const char *pattern1 = "_FILE_VERSION_";
1502 const char *pattern2 = "?.?";
1503 const int len_cookie = strlen(cookie);
1504 const int len_pattern1 = strlen(pattern1);
1505 const int len_pattern2 = strlen(pattern2);
1506 const int len_pattern = len_pattern1 + len_pattern2;
1507 int version_major, version_minor;
1509 if (len_cookie <= len_pattern)
1512 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1513 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1515 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1518 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1519 ptr_cookie2[1] != '.' ||
1520 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1523 version_major = ptr_cookie2[0] - '0';
1524 version_minor = ptr_cookie2[2] - '0';
1526 return VERSION_IDENT(version_major, version_minor, 0, 0);
1529 boolean checkCookieString(const char *cookie, const char *template)
1531 const char *pattern = "_FILE_VERSION_?.?";
1532 const int len_cookie = strlen(cookie);
1533 const int len_template = strlen(template);
1534 const int len_pattern = strlen(pattern);
1536 if (len_cookie != len_template)
1539 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1546 /* ------------------------------------------------------------------------- */
1547 /* setup file list and hash handling functions */
1548 /* ------------------------------------------------------------------------- */
1550 char *getFormattedSetupEntry(char *token, char *value)
1553 static char entry[MAX_LINE_LEN];
1555 /* if value is an empty string, just return token without value */
1559 /* start with the token and some spaces to format output line */
1560 sprintf(entry, "%s:", token);
1561 for (i = strlen(entry); i < token_value_position; i++)
1564 /* continue with the token's value */
1565 strcat(entry, value);
1570 SetupFileList *newSetupFileList(char *token, char *value)
1572 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1574 new->token = getStringCopy(token);
1575 new->value = getStringCopy(value);
1582 void freeSetupFileList(SetupFileList *list)
1587 checked_free(list->token);
1588 checked_free(list->value);
1591 freeSetupFileList(list->next);
1596 char *getListEntry(SetupFileList *list, char *token)
1601 if (strEqual(list->token, token))
1604 return getListEntry(list->next, token);
1607 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1612 if (strEqual(list->token, token))
1614 checked_free(list->value);
1616 list->value = getStringCopy(value);
1620 else if (list->next == NULL)
1621 return (list->next = newSetupFileList(token, value));
1623 return setListEntry(list->next, token, value);
1626 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1631 if (list->next == NULL)
1632 return (list->next = newSetupFileList(token, value));
1634 return addListEntry(list->next, token, value);
1637 #if ENABLE_UNUSED_CODE
1639 static void printSetupFileList(SetupFileList *list)
1644 printf("token: '%s'\n", list->token);
1645 printf("value: '%s'\n", list->value);
1647 printSetupFileList(list->next);
1653 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1654 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1655 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1656 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1658 #define insert_hash_entry hashtable_insert
1659 #define search_hash_entry hashtable_search
1660 #define change_hash_entry hashtable_change
1661 #define remove_hash_entry hashtable_remove
1664 unsigned int get_hash_from_key(void *key)
1669 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1670 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1671 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1672 it works better than many other constants, prime or not) has never been
1673 adequately explained.
1675 If you just want to have a good hash function, and cannot wait, djb2
1676 is one of the best string hash functions i know. It has excellent
1677 distribution and speed on many different sets of keys and table sizes.
1678 You are not likely to do better with one of the "well known" functions
1679 such as PJW, K&R, etc.
1681 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1684 char *str = (char *)key;
1685 unsigned int hash = 5381;
1688 while ((c = *str++))
1689 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1694 static int keys_are_equal(void *key1, void *key2)
1696 return (strEqual((char *)key1, (char *)key2));
1699 SetupFileHash *newSetupFileHash()
1701 SetupFileHash *new_hash =
1702 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1704 if (new_hash == NULL)
1705 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1710 void freeSetupFileHash(SetupFileHash *hash)
1715 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1718 char *getHashEntry(SetupFileHash *hash, char *token)
1723 return search_hash_entry(hash, token);
1726 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1733 value_copy = getStringCopy(value);
1735 /* change value; if it does not exist, insert it as new */
1736 if (!change_hash_entry(hash, token, value_copy))
1737 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1738 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1741 char *removeHashEntry(SetupFileHash *hash, char *token)
1746 return remove_hash_entry(hash, token);
1749 #if ENABLE_UNUSED_CODE
1751 static void printSetupFileHash(SetupFileHash *hash)
1753 BEGIN_HASH_ITERATION(hash, itr)
1755 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1756 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1758 END_HASH_ITERATION(hash, itr)
1763 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1764 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1765 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1767 static boolean token_value_separator_found = FALSE;
1768 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1769 static boolean token_value_separator_warning = FALSE;
1771 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1772 static boolean token_already_exists_warning = FALSE;
1775 static boolean getTokenValueFromSetupLineExt(char *line,
1776 char **token_ptr, char **value_ptr,
1777 char *filename, char *line_raw,
1779 boolean separator_required)
1781 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1782 char *token, *value, *line_ptr;
1784 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1785 if (line_raw == NULL)
1787 strncpy(line_copy, line, MAX_LINE_LEN);
1788 line_copy[MAX_LINE_LEN] = '\0';
1791 strcpy(line_raw_copy, line_copy);
1792 line_raw = line_raw_copy;
1795 /* cut trailing comment from input line */
1796 for (line_ptr = line; *line_ptr; line_ptr++)
1798 if (*line_ptr == '#')
1805 /* cut trailing whitespaces from input line */
1806 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1807 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1810 /* ignore empty lines */
1814 /* cut leading whitespaces from token */
1815 for (token = line; *token; token++)
1816 if (*token != ' ' && *token != '\t')
1819 /* start with empty value as reliable default */
1822 token_value_separator_found = FALSE;
1824 /* find end of token to determine start of value */
1825 for (line_ptr = token; *line_ptr; line_ptr++)
1827 /* first look for an explicit token/value separator, like ':' or '=' */
1828 if (*line_ptr == ':' || *line_ptr == '=')
1830 *line_ptr = '\0'; /* terminate token string */
1831 value = line_ptr + 1; /* set beginning of value */
1833 token_value_separator_found = TRUE;
1839 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1840 /* fallback: if no token/value separator found, also allow whitespaces */
1841 if (!token_value_separator_found && !separator_required)
1843 for (line_ptr = token; *line_ptr; line_ptr++)
1845 if (*line_ptr == ' ' || *line_ptr == '\t')
1847 *line_ptr = '\0'; /* terminate token string */
1848 value = line_ptr + 1; /* set beginning of value */
1850 token_value_separator_found = TRUE;
1856 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1857 if (token_value_separator_found)
1859 if (!token_value_separator_warning)
1861 Error(ERR_INFO_LINE, "-");
1863 if (filename != NULL)
1865 Error(ERR_WARN, "missing token/value separator(s) in config file:");
1866 Error(ERR_INFO, "- config file: '%s'", filename);
1870 Error(ERR_WARN, "missing token/value separator(s):");
1873 token_value_separator_warning = TRUE;
1876 if (filename != NULL)
1877 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
1879 Error(ERR_INFO, "- line: '%s'", line_raw);
1885 /* cut trailing whitespaces from token */
1886 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
1887 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1890 /* cut leading whitespaces from value */
1891 for (; *value; value++)
1892 if (*value != ' ' && *value != '\t')
1901 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
1903 /* while the internal (old) interface does not require a token/value
1904 separator (for downwards compatibility with existing files which
1905 don't use them), it is mandatory for the external (new) interface */
1907 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
1910 static boolean loadSetupFileData(void *setup_file_data, char *filename,
1911 boolean top_recursion_level, boolean is_hash)
1913 static SetupFileHash *include_filename_hash = NULL;
1914 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
1915 char *token, *value, *line_ptr;
1916 void *insert_ptr = NULL;
1917 boolean read_continued_line = FALSE;
1919 int line_nr = 0, token_count = 0, include_count = 0;
1921 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1922 token_value_separator_warning = FALSE;
1925 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1926 token_already_exists_warning = FALSE;
1929 if (!(file = openFile(filename, MODE_READ)))
1931 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1936 /* use "insert pointer" to store list end for constant insertion complexity */
1938 insert_ptr = setup_file_data;
1940 /* on top invocation, create hash to mark included files (to prevent loops) */
1941 if (top_recursion_level)
1942 include_filename_hash = newSetupFileHash();
1944 /* mark this file as already included (to prevent including it again) */
1945 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
1947 while (!checkEndOfFile(file))
1949 /* read next line of input file */
1950 if (!getStringFromFile(file, line, MAX_LINE_LEN))
1953 /* check if line was completely read and is terminated by line break */
1954 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
1957 /* cut trailing line break (this can be newline and/or carriage return) */
1958 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1959 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
1962 /* copy raw input line for later use (mainly debugging output) */
1963 strcpy(line_raw, line);
1965 if (read_continued_line)
1967 /* append new line to existing line, if there is enough space */
1968 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
1969 strcat(previous_line, line_ptr);
1971 strcpy(line, previous_line); /* copy storage buffer to line */
1973 read_continued_line = FALSE;
1976 /* if the last character is '\', continue at next line */
1977 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
1979 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
1980 strcpy(previous_line, line); /* copy line to storage buffer */
1982 read_continued_line = TRUE;
1987 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
1988 line_raw, line_nr, FALSE))
1993 if (strEqual(token, "include"))
1995 if (getHashEntry(include_filename_hash, value) == NULL)
1997 char *basepath = getBasePath(filename);
1998 char *basename = getBaseName(value);
1999 char *filename_include = getPath2(basepath, basename);
2001 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2005 free(filename_include);
2011 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2018 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2020 getHashEntry((SetupFileHash *)setup_file_data, token);
2022 if (old_value != NULL)
2024 if (!token_already_exists_warning)
2026 Error(ERR_INFO_LINE, "-");
2027 Error(ERR_WARN, "duplicate token(s) found in config file:");
2028 Error(ERR_INFO, "- config file: '%s'", filename);
2030 token_already_exists_warning = TRUE;
2033 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2034 Error(ERR_INFO, " old value: '%s'", old_value);
2035 Error(ERR_INFO, " new value: '%s'", value);
2039 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2043 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2053 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2054 if (token_value_separator_warning)
2055 Error(ERR_INFO_LINE, "-");
2058 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2059 if (token_already_exists_warning)
2060 Error(ERR_INFO_LINE, "-");
2063 if (token_count == 0 && include_count == 0)
2064 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2066 if (top_recursion_level)
2067 freeSetupFileHash(include_filename_hash);
2072 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2076 if (!(file = fopen(filename, MODE_WRITE)))
2078 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2083 BEGIN_HASH_ITERATION(hash, itr)
2085 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2086 HASH_ITERATION_VALUE(itr)));
2088 END_HASH_ITERATION(hash, itr)
2093 SetupFileList *loadSetupFileList(char *filename)
2095 SetupFileList *setup_file_list = newSetupFileList("", "");
2096 SetupFileList *first_valid_list_entry;
2098 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2100 freeSetupFileList(setup_file_list);
2105 first_valid_list_entry = setup_file_list->next;
2107 /* free empty list header */
2108 setup_file_list->next = NULL;
2109 freeSetupFileList(setup_file_list);
2111 return first_valid_list_entry;
2114 SetupFileHash *loadSetupFileHash(char *filename)
2116 SetupFileHash *setup_file_hash = newSetupFileHash();
2118 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2120 freeSetupFileHash(setup_file_hash);
2125 return setup_file_hash;
2129 /* ========================================================================= */
2130 /* setup file stuff */
2131 /* ========================================================================= */
2133 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2134 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2135 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2137 /* level directory info */
2138 #define LEVELINFO_TOKEN_IDENTIFIER 0
2139 #define LEVELINFO_TOKEN_NAME 1
2140 #define LEVELINFO_TOKEN_NAME_SORTING 2
2141 #define LEVELINFO_TOKEN_AUTHOR 3
2142 #define LEVELINFO_TOKEN_YEAR 4
2143 #define LEVELINFO_TOKEN_IMPORTED_FROM 5
2144 #define LEVELINFO_TOKEN_IMPORTED_BY 6
2145 #define LEVELINFO_TOKEN_TESTED_BY 7
2146 #define LEVELINFO_TOKEN_LEVELS 8
2147 #define LEVELINFO_TOKEN_FIRST_LEVEL 9
2148 #define LEVELINFO_TOKEN_SORT_PRIORITY 10
2149 #define LEVELINFO_TOKEN_LATEST_ENGINE 11
2150 #define LEVELINFO_TOKEN_LEVEL_GROUP 12
2151 #define LEVELINFO_TOKEN_READONLY 13
2152 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 14
2153 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 15
2154 #define LEVELINFO_TOKEN_GRAPHICS_SET 16
2155 #define LEVELINFO_TOKEN_SOUNDS_SET 17
2156 #define LEVELINFO_TOKEN_MUSIC_SET 18
2157 #define LEVELINFO_TOKEN_FILENAME 19
2158 #define LEVELINFO_TOKEN_FILETYPE 20
2159 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 21
2160 #define LEVELINFO_TOKEN_HANDICAP 22
2161 #define LEVELINFO_TOKEN_SKIP_LEVELS 23
2163 #define NUM_LEVELINFO_TOKENS 24
2165 static LevelDirTree ldi;
2167 static struct TokenInfo levelinfo_tokens[] =
2169 /* level directory info */
2170 { TYPE_STRING, &ldi.identifier, "identifier" },
2171 { TYPE_STRING, &ldi.name, "name" },
2172 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2173 { TYPE_STRING, &ldi.author, "author" },
2174 { TYPE_STRING, &ldi.year, "year" },
2175 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2176 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2177 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2178 { TYPE_INTEGER, &ldi.levels, "levels" },
2179 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2180 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2181 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2182 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2183 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2184 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2185 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2186 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2187 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2188 { TYPE_STRING, &ldi.music_set, "music_set" },
2189 { TYPE_STRING, &ldi.level_filename, "filename" },
2190 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2191 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2192 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2193 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2196 static struct TokenInfo artworkinfo_tokens[] =
2198 /* artwork directory info */
2199 { TYPE_STRING, &ldi.identifier, "identifier" },
2200 { TYPE_STRING, &ldi.subdir, "subdir" },
2201 { TYPE_STRING, &ldi.name, "name" },
2202 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2203 { TYPE_STRING, &ldi.author, "author" },
2204 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2205 { TYPE_STRING, &ldi.basepath, "basepath" },
2206 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2207 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2208 { TYPE_INTEGER, &ldi.color, "color" },
2209 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2214 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2218 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2219 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2220 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2221 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2224 ti->node_parent = NULL;
2225 ti->node_group = NULL;
2232 ti->fullpath = NULL;
2233 ti->basepath = NULL;
2234 ti->identifier = NULL;
2235 ti->name = getStringCopy(ANONYMOUS_NAME);
2236 ti->name_sorting = NULL;
2237 ti->author = getStringCopy(ANONYMOUS_NAME);
2240 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2241 ti->latest_engine = FALSE; /* default: get from level */
2242 ti->parent_link = FALSE;
2243 ti->in_user_dir = FALSE;
2244 ti->user_defined = FALSE;
2246 ti->class_desc = NULL;
2248 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2250 if (ti->type == TREE_TYPE_LEVEL_DIR)
2252 ti->imported_from = NULL;
2253 ti->imported_by = NULL;
2254 ti->tested_by = NULL;
2256 ti->graphics_set_ecs = NULL;
2257 ti->graphics_set_aga = NULL;
2258 ti->graphics_set = NULL;
2259 ti->sounds_set = NULL;
2260 ti->music_set = NULL;
2261 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2262 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2263 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2265 ti->level_filename = NULL;
2266 ti->level_filetype = NULL;
2268 ti->special_flags = NULL;
2271 ti->first_level = 0;
2273 ti->level_group = FALSE;
2274 ti->handicap_level = 0;
2275 ti->readonly = TRUE;
2276 ti->handicap = TRUE;
2277 ti->skip_levels = FALSE;
2281 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2285 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2287 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2292 /* copy all values from the parent structure */
2294 ti->type = parent->type;
2296 ti->node_top = parent->node_top;
2297 ti->node_parent = parent;
2298 ti->node_group = NULL;
2305 ti->fullpath = NULL;
2306 ti->basepath = NULL;
2307 ti->identifier = NULL;
2308 ti->name = getStringCopy(ANONYMOUS_NAME);
2309 ti->name_sorting = NULL;
2310 ti->author = getStringCopy(parent->author);
2311 ti->year = getStringCopy(parent->year);
2313 ti->sort_priority = parent->sort_priority;
2314 ti->latest_engine = parent->latest_engine;
2315 ti->parent_link = FALSE;
2316 ti->in_user_dir = parent->in_user_dir;
2317 ti->user_defined = parent->user_defined;
2318 ti->color = parent->color;
2319 ti->class_desc = getStringCopy(parent->class_desc);
2321 ti->infotext = getStringCopy(parent->infotext);
2323 if (ti->type == TREE_TYPE_LEVEL_DIR)
2325 ti->imported_from = getStringCopy(parent->imported_from);
2326 ti->imported_by = getStringCopy(parent->imported_by);
2327 ti->tested_by = getStringCopy(parent->tested_by);
2329 ti->graphics_set_ecs = NULL;
2330 ti->graphics_set_aga = NULL;
2331 ti->graphics_set = NULL;
2332 ti->sounds_set = NULL;
2333 ti->music_set = NULL;
2334 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2335 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2336 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2338 ti->level_filename = NULL;
2339 ti->level_filetype = NULL;
2341 ti->special_flags = getStringCopy(parent->special_flags);
2344 ti->first_level = 0;
2346 ti->level_group = FALSE;
2347 ti->handicap_level = 0;
2348 ti->readonly = parent->readonly;
2349 ti->handicap = TRUE;
2350 ti->skip_levels = FALSE;
2354 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2356 TreeInfo *ti_copy = newTreeInfo();
2358 /* copy all values from the original structure */
2360 ti_copy->type = ti->type;
2362 ti_copy->node_top = ti->node_top;
2363 ti_copy->node_parent = ti->node_parent;
2364 ti_copy->node_group = ti->node_group;
2365 ti_copy->next = ti->next;
2367 ti_copy->cl_first = ti->cl_first;
2368 ti_copy->cl_cursor = ti->cl_cursor;
2370 ti_copy->subdir = getStringCopy(ti->subdir);
2371 ti_copy->fullpath = getStringCopy(ti->fullpath);
2372 ti_copy->basepath = getStringCopy(ti->basepath);
2373 ti_copy->identifier = getStringCopy(ti->identifier);
2374 ti_copy->name = getStringCopy(ti->name);
2375 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2376 ti_copy->author = getStringCopy(ti->author);
2377 ti_copy->year = getStringCopy(ti->year);
2378 ti_copy->imported_from = getStringCopy(ti->imported_from);
2379 ti_copy->imported_by = getStringCopy(ti->imported_by);
2380 ti_copy->tested_by = getStringCopy(ti->tested_by);
2382 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2383 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2384 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2385 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2386 ti_copy->music_set = getStringCopy(ti->music_set);
2387 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2388 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2389 ti_copy->music_path = getStringCopy(ti->music_path);
2391 ti_copy->level_filename = getStringCopy(ti->level_filename);
2392 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2394 ti_copy->special_flags = getStringCopy(ti->special_flags);
2396 ti_copy->levels = ti->levels;
2397 ti_copy->first_level = ti->first_level;
2398 ti_copy->last_level = ti->last_level;
2399 ti_copy->sort_priority = ti->sort_priority;
2401 ti_copy->latest_engine = ti->latest_engine;
2403 ti_copy->level_group = ti->level_group;
2404 ti_copy->parent_link = ti->parent_link;
2405 ti_copy->in_user_dir = ti->in_user_dir;
2406 ti_copy->user_defined = ti->user_defined;
2407 ti_copy->readonly = ti->readonly;
2408 ti_copy->handicap = ti->handicap;
2409 ti_copy->skip_levels = ti->skip_levels;
2411 ti_copy->color = ti->color;
2412 ti_copy->class_desc = getStringCopy(ti->class_desc);
2413 ti_copy->handicap_level = ti->handicap_level;
2415 ti_copy->infotext = getStringCopy(ti->infotext);
2420 void freeTreeInfo(TreeInfo *ti)
2425 checked_free(ti->subdir);
2426 checked_free(ti->fullpath);
2427 checked_free(ti->basepath);
2428 checked_free(ti->identifier);
2430 checked_free(ti->name);
2431 checked_free(ti->name_sorting);
2432 checked_free(ti->author);
2433 checked_free(ti->year);
2435 checked_free(ti->class_desc);
2437 checked_free(ti->infotext);
2439 if (ti->type == TREE_TYPE_LEVEL_DIR)
2441 checked_free(ti->imported_from);
2442 checked_free(ti->imported_by);
2443 checked_free(ti->tested_by);
2445 checked_free(ti->graphics_set_ecs);
2446 checked_free(ti->graphics_set_aga);
2447 checked_free(ti->graphics_set);
2448 checked_free(ti->sounds_set);
2449 checked_free(ti->music_set);
2451 checked_free(ti->graphics_path);
2452 checked_free(ti->sounds_path);
2453 checked_free(ti->music_path);
2455 checked_free(ti->level_filename);
2456 checked_free(ti->level_filetype);
2458 checked_free(ti->special_flags);
2461 // recursively free child node
2463 freeTreeInfo(ti->node_group);
2465 // recursively free next node
2467 freeTreeInfo(ti->next);
2472 void setSetupInfo(struct TokenInfo *token_info,
2473 int token_nr, char *token_value)
2475 int token_type = token_info[token_nr].type;
2476 void *setup_value = token_info[token_nr].value;
2478 if (token_value == NULL)
2481 /* set setup field to corresponding token value */
2486 *(boolean *)setup_value = get_boolean_from_string(token_value);
2490 *(int *)setup_value = get_switch3_from_string(token_value);
2494 *(Key *)setup_value = getKeyFromKeyName(token_value);
2498 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2502 *(int *)setup_value = get_integer_from_string(token_value);
2506 checked_free(*(char **)setup_value);
2507 *(char **)setup_value = getStringCopy(token_value);
2515 static int compareTreeInfoEntries(const void *object1, const void *object2)
2517 const TreeInfo *entry1 = *((TreeInfo **)object1);
2518 const TreeInfo *entry2 = *((TreeInfo **)object2);
2519 int class_sorting1 = 0, class_sorting2 = 0;
2522 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2524 class_sorting1 = LEVELSORTING(entry1);
2525 class_sorting2 = LEVELSORTING(entry2);
2527 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2528 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2529 entry1->type == TREE_TYPE_MUSIC_DIR)
2531 class_sorting1 = ARTWORKSORTING(entry1);
2532 class_sorting2 = ARTWORKSORTING(entry2);
2535 if (entry1->parent_link || entry2->parent_link)
2536 compare_result = (entry1->parent_link ? -1 : +1);
2537 else if (entry1->sort_priority == entry2->sort_priority)
2539 char *name1 = getStringToLower(entry1->name_sorting);
2540 char *name2 = getStringToLower(entry2->name_sorting);
2542 compare_result = strcmp(name1, name2);
2547 else if (class_sorting1 == class_sorting2)
2548 compare_result = entry1->sort_priority - entry2->sort_priority;
2550 compare_result = class_sorting1 - class_sorting2;
2552 return compare_result;
2555 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2559 if (node_parent == NULL)
2562 ti_new = newTreeInfo();
2563 setTreeInfoToDefaults(ti_new, node_parent->type);
2565 ti_new->node_parent = node_parent;
2566 ti_new->parent_link = TRUE;
2568 setString(&ti_new->identifier, node_parent->identifier);
2569 setString(&ti_new->name, ".. (parent directory)");
2570 setString(&ti_new->name_sorting, ti_new->name);
2572 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2573 setString(&ti_new->fullpath, node_parent->fullpath);
2575 ti_new->sort_priority = node_parent->sort_priority;
2576 ti_new->latest_engine = node_parent->latest_engine;
2578 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2580 pushTreeInfo(&node_parent->node_group, ti_new);
2585 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2587 TreeInfo *ti_new, *ti_new2;
2589 if (node_first == NULL)
2592 ti_new = newTreeInfo();
2593 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2595 ti_new->node_parent = NULL;
2596 ti_new->parent_link = FALSE;
2598 setString(&ti_new->identifier, node_first->identifier);
2599 setString(&ti_new->name, "level sets");
2600 setString(&ti_new->name_sorting, ti_new->name);
2602 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2603 setString(&ti_new->fullpath, node_first->fullpath);
2605 ti_new->sort_priority = node_first->sort_priority;;
2606 ti_new->latest_engine = node_first->latest_engine;
2608 setString(&ti_new->class_desc, "level sets");
2610 ti_new->node_group = node_first;
2611 ti_new->level_group = TRUE;
2613 ti_new2 = createParentTreeInfoNode(ti_new);
2615 setString(&ti_new2->name, ".. (main menu)");
2616 setString(&ti_new2->name_sorting, ti_new2->name);
2622 /* -------------------------------------------------------------------------- */
2623 /* functions for handling level and custom artwork info cache */
2624 /* -------------------------------------------------------------------------- */
2626 static void LoadArtworkInfoCache()
2628 InitCacheDirectory();
2630 if (artworkinfo_cache_old == NULL)
2632 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2634 /* try to load artwork info hash from already existing cache file */
2635 artworkinfo_cache_old = loadSetupFileHash(filename);
2637 /* if no artwork info cache file was found, start with empty hash */
2638 if (artworkinfo_cache_old == NULL)
2639 artworkinfo_cache_old = newSetupFileHash();
2644 if (artworkinfo_cache_new == NULL)
2645 artworkinfo_cache_new = newSetupFileHash();
2648 static void SaveArtworkInfoCache()
2650 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2652 InitCacheDirectory();
2654 saveSetupFileHash(artworkinfo_cache_new, filename);
2659 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2661 static char *prefix = NULL;
2663 checked_free(prefix);
2665 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2670 /* (identical to above function, but separate string buffer needed -- nasty) */
2671 static char *getCacheToken(char *prefix, char *suffix)
2673 static char *token = NULL;
2675 checked_free(token);
2677 token = getStringCat2WithSeparator(prefix, suffix, ".");
2682 static char *getFileTimestampString(char *filename)
2684 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2687 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2689 struct stat file_status;
2691 if (timestamp_string == NULL)
2694 if (stat(filename, &file_status) != 0) /* cannot stat file */
2697 return (file_status.st_mtime != atoi(timestamp_string));
2700 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2702 char *identifier = level_node->subdir;
2703 char *type_string = ARTWORK_DIRECTORY(type);
2704 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2705 char *token_main = getCacheToken(token_prefix, "CACHED");
2706 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2707 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2708 TreeInfo *artwork_info = NULL;
2710 if (!use_artworkinfo_cache)
2717 artwork_info = newTreeInfo();
2718 setTreeInfoToDefaults(artwork_info, type);
2720 /* set all structure fields according to the token/value pairs */
2721 ldi = *artwork_info;
2722 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2724 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2725 char *value = getHashEntry(artworkinfo_cache_old, token);
2727 setSetupInfo(artworkinfo_tokens, i, value);
2729 /* check if cache entry for this item is invalid or incomplete */
2732 Error(ERR_WARN, "cache entry '%s' invalid", token);
2738 *artwork_info = ldi;
2743 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2744 LEVELINFO_FILENAME);
2745 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2746 ARTWORKINFO_FILENAME(type));
2748 /* check if corresponding "levelinfo.conf" file has changed */
2749 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2750 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2752 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2755 /* check if corresponding "<artworkinfo>.conf" file has changed */
2756 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2757 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2759 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2762 checked_free(filename_levelinfo);
2763 checked_free(filename_artworkinfo);
2766 if (!cached && artwork_info != NULL)
2768 freeTreeInfo(artwork_info);
2773 return artwork_info;
2776 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2777 LevelDirTree *level_node, int type)
2779 char *identifier = level_node->subdir;
2780 char *type_string = ARTWORK_DIRECTORY(type);
2781 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2782 char *token_main = getCacheToken(token_prefix, "CACHED");
2783 boolean set_cache_timestamps = TRUE;
2786 setHashEntry(artworkinfo_cache_new, token_main, "true");
2788 if (set_cache_timestamps)
2790 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2791 LEVELINFO_FILENAME);
2792 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2793 ARTWORKINFO_FILENAME(type));
2794 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2795 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2797 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2798 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2800 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2801 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2803 checked_free(filename_levelinfo);
2804 checked_free(filename_artworkinfo);
2805 checked_free(timestamp_levelinfo);
2806 checked_free(timestamp_artworkinfo);
2809 ldi = *artwork_info;
2810 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2812 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2813 char *value = getSetupValue(artworkinfo_tokens[i].type,
2814 artworkinfo_tokens[i].value);
2816 setHashEntry(artworkinfo_cache_new, token, value);
2821 /* -------------------------------------------------------------------------- */
2822 /* functions for loading level info and custom artwork info */
2823 /* -------------------------------------------------------------------------- */
2825 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2826 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2828 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2829 TreeInfo *node_parent,
2830 char *level_directory,
2831 char *directory_name)
2833 char *directory_path = getPath2(level_directory, directory_name);
2834 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2835 SetupFileHash *setup_file_hash;
2836 LevelDirTree *leveldir_new = NULL;
2839 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2840 if (!options.debug && !fileExists(filename))
2842 free(directory_path);
2848 setup_file_hash = loadSetupFileHash(filename);
2850 if (setup_file_hash == NULL)
2852 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
2854 free(directory_path);
2860 leveldir_new = newTreeInfo();
2863 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
2865 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
2867 leveldir_new->subdir = getStringCopy(directory_name);
2869 /* set all structure fields according to the token/value pairs */
2870 ldi = *leveldir_new;
2871 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
2872 setSetupInfo(levelinfo_tokens, i,
2873 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
2874 *leveldir_new = ldi;
2876 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
2877 setString(&leveldir_new->name, leveldir_new->subdir);
2879 if (leveldir_new->identifier == NULL)
2880 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
2882 if (leveldir_new->name_sorting == NULL)
2883 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
2885 if (node_parent == NULL) /* top level group */
2887 leveldir_new->basepath = getStringCopy(level_directory);
2888 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
2890 else /* sub level group */
2892 leveldir_new->basepath = getStringCopy(node_parent->basepath);
2893 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2896 leveldir_new->last_level =
2897 leveldir_new->first_level + leveldir_new->levels - 1;
2899 leveldir_new->in_user_dir =
2900 (!strEqual(leveldir_new->basepath, options.level_directory));
2902 /* adjust some settings if user's private level directory was detected */
2903 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
2904 leveldir_new->in_user_dir &&
2905 (strEqual(leveldir_new->subdir, getLoginName()) ||
2906 strEqual(leveldir_new->name, getLoginName()) ||
2907 strEqual(leveldir_new->author, getRealName())))
2909 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
2910 leveldir_new->readonly = FALSE;
2913 leveldir_new->user_defined =
2914 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
2916 leveldir_new->color = LEVELCOLOR(leveldir_new);
2918 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
2920 leveldir_new->handicap_level = /* set handicap to default value */
2921 (leveldir_new->user_defined || !leveldir_new->handicap ?
2922 leveldir_new->last_level : leveldir_new->first_level);
2924 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
2926 pushTreeInfo(node_first, leveldir_new);
2928 freeSetupFileHash(setup_file_hash);
2930 if (leveldir_new->level_group)
2932 /* create node to link back to current level directory */
2933 createParentTreeInfoNode(leveldir_new);
2935 /* recursively step into sub-directory and look for more level series */
2936 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
2937 leveldir_new, directory_path);
2940 free(directory_path);
2946 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
2947 TreeInfo *node_parent,
2948 char *level_directory)
2951 DirectoryEntry *dir_entry;
2952 boolean valid_entry_found = FALSE;
2954 if ((dir = openDirectory(level_directory)) == NULL)
2956 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2961 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
2963 char *directory_name = dir_entry->basename;
2964 char *directory_path = getPath2(level_directory, directory_name);
2966 /* skip entries for current and parent directory */
2967 if (strEqual(directory_name, ".") ||
2968 strEqual(directory_name, ".."))
2970 free(directory_path);
2975 /* find out if directory entry is itself a directory */
2976 if (!dir_entry->is_directory) /* not a directory */
2978 free(directory_path);
2983 free(directory_path);
2985 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
2986 strEqual(directory_name, SOUNDS_DIRECTORY) ||
2987 strEqual(directory_name, MUSIC_DIRECTORY))
2990 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
2995 closeDirectory(dir);
2997 /* special case: top level directory may directly contain "levelinfo.conf" */
2998 if (node_parent == NULL && !valid_entry_found)
3000 /* check if this directory directly contains a file "levelinfo.conf" */
3001 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3002 level_directory, ".");
3005 if (!valid_entry_found)
3006 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3010 boolean AdjustGraphicsForEMC()
3012 boolean settings_changed = FALSE;
3014 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3015 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3017 return settings_changed;
3020 void LoadLevelInfo()
3022 InitUserLevelDirectory(getLoginName());
3024 DrawInitText("Loading level series", 120, FC_GREEN);
3026 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3027 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3029 leveldir_first = createTopTreeInfoNode(leveldir_first);
3031 /* after loading all level set information, clone the level directory tree
3032 and remove all level sets without levels (these may still contain artwork
3033 to be offered in the setup menu as "custom artwork", and are therefore
3034 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3035 leveldir_first_all = leveldir_first;
3036 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3038 AdjustGraphicsForEMC();
3040 /* before sorting, the first entries will be from the user directory */
3041 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3043 if (leveldir_first == NULL)
3044 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3046 sortTreeInfo(&leveldir_first);
3048 #if ENABLE_UNUSED_CODE
3049 dumpTreeInfo(leveldir_first, 0);
3053 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3054 TreeInfo *node_parent,
3055 char *base_directory,
3056 char *directory_name, int type)
3058 char *directory_path = getPath2(base_directory, directory_name);
3059 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3060 SetupFileHash *setup_file_hash = NULL;
3061 TreeInfo *artwork_new = NULL;
3064 if (fileExists(filename))
3065 setup_file_hash = loadSetupFileHash(filename);
3067 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3070 DirectoryEntry *dir_entry;
3071 boolean valid_file_found = FALSE;
3073 if ((dir = openDirectory(directory_path)) != NULL)
3075 while ((dir_entry = readDirectory(dir)) != NULL)
3077 if (FileIsArtworkType(dir_entry->filename, type))
3079 valid_file_found = TRUE;
3085 closeDirectory(dir);
3088 if (!valid_file_found)
3090 if (!strEqual(directory_name, "."))
3091 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3093 free(directory_path);
3100 artwork_new = newTreeInfo();
3103 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3105 setTreeInfoToDefaults(artwork_new, type);
3107 artwork_new->subdir = getStringCopy(directory_name);
3109 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3111 /* set all structure fields according to the token/value pairs */
3113 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3114 setSetupInfo(levelinfo_tokens, i,
3115 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3118 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3119 setString(&artwork_new->name, artwork_new->subdir);
3121 if (artwork_new->identifier == NULL)
3122 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3124 if (artwork_new->name_sorting == NULL)
3125 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3128 if (node_parent == NULL) /* top level group */
3130 artwork_new->basepath = getStringCopy(base_directory);
3131 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3133 else /* sub level group */
3135 artwork_new->basepath = getStringCopy(node_parent->basepath);
3136 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3139 artwork_new->in_user_dir =
3140 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3142 /* (may use ".sort_priority" from "setup_file_hash" above) */
3143 artwork_new->color = ARTWORKCOLOR(artwork_new);
3145 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3147 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3149 if (strEqual(artwork_new->subdir, "."))
3151 if (artwork_new->user_defined)
3153 setString(&artwork_new->identifier, "private");
3154 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3158 setString(&artwork_new->identifier, "classic");
3159 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3162 /* set to new values after changing ".sort_priority" */
3163 artwork_new->color = ARTWORKCOLOR(artwork_new);
3165 setString(&artwork_new->class_desc,
3166 getLevelClassDescription(artwork_new));
3170 setString(&artwork_new->identifier, artwork_new->subdir);
3173 setString(&artwork_new->name, artwork_new->identifier);
3174 setString(&artwork_new->name_sorting, artwork_new->name);
3177 pushTreeInfo(node_first, artwork_new);
3179 freeSetupFileHash(setup_file_hash);
3181 free(directory_path);
3187 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3188 TreeInfo *node_parent,
3189 char *base_directory, int type)
3192 DirectoryEntry *dir_entry;
3193 boolean valid_entry_found = FALSE;
3195 if ((dir = openDirectory(base_directory)) == NULL)
3197 /* display error if directory is main "options.graphics_directory" etc. */
3198 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3199 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3204 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3206 char *directory_name = dir_entry->basename;
3207 char *directory_path = getPath2(base_directory, directory_name);
3209 /* skip directory entries for current and parent directory */
3210 if (strEqual(directory_name, ".") ||
3211 strEqual(directory_name, ".."))
3213 free(directory_path);
3218 /* skip directory entries which are not a directory */
3219 if (!dir_entry->is_directory) /* not a directory */
3221 free(directory_path);
3226 free(directory_path);
3228 /* check if this directory contains artwork with or without config file */
3229 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3231 directory_name, type);
3234 closeDirectory(dir);
3236 /* check if this directory directly contains artwork itself */
3237 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3238 base_directory, ".",
3240 if (!valid_entry_found)
3241 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3245 static TreeInfo *getDummyArtworkInfo(int type)
3247 /* this is only needed when there is completely no artwork available */
3248 TreeInfo *artwork_new = newTreeInfo();
3250 setTreeInfoToDefaults(artwork_new, type);
3252 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3253 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3254 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3256 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3257 setString(&artwork_new->name, UNDEFINED_FILENAME);
3258 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3263 void LoadArtworkInfo()
3265 LoadArtworkInfoCache();
3267 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3269 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3270 options.graphics_directory,
3271 TREE_TYPE_GRAPHICS_DIR);
3272 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3273 getUserGraphicsDir(),
3274 TREE_TYPE_GRAPHICS_DIR);
3276 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3277 options.sounds_directory,
3278 TREE_TYPE_SOUNDS_DIR);
3279 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3281 TREE_TYPE_SOUNDS_DIR);
3283 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3284 options.music_directory,
3285 TREE_TYPE_MUSIC_DIR);
3286 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3288 TREE_TYPE_MUSIC_DIR);
3290 if (artwork.gfx_first == NULL)
3291 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3292 if (artwork.snd_first == NULL)
3293 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3294 if (artwork.mus_first == NULL)
3295 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3297 /* before sorting, the first entries will be from the user directory */
3298 artwork.gfx_current =
3299 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3300 if (artwork.gfx_current == NULL)
3301 artwork.gfx_current =
3302 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3303 if (artwork.gfx_current == NULL)
3304 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3306 artwork.snd_current =
3307 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3308 if (artwork.snd_current == NULL)
3309 artwork.snd_current =
3310 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3311 if (artwork.snd_current == NULL)
3312 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3314 artwork.mus_current =
3315 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3316 if (artwork.mus_current == NULL)
3317 artwork.mus_current =
3318 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3319 if (artwork.mus_current == NULL)
3320 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3322 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3323 artwork.snd_current_identifier = artwork.snd_current->identifier;
3324 artwork.mus_current_identifier = artwork.mus_current->identifier;
3326 #if ENABLE_UNUSED_CODE
3327 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3328 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3329 printf("music set == %s\n\n", artwork.mus_current_identifier);
3332 sortTreeInfo(&artwork.gfx_first);
3333 sortTreeInfo(&artwork.snd_first);
3334 sortTreeInfo(&artwork.mus_first);
3336 #if ENABLE_UNUSED_CODE
3337 dumpTreeInfo(artwork.gfx_first, 0);
3338 dumpTreeInfo(artwork.snd_first, 0);
3339 dumpTreeInfo(artwork.mus_first, 0);
3343 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3344 LevelDirTree *level_node)
3346 int type = (*artwork_node)->type;
3348 /* recursively check all level directories for artwork sub-directories */
3352 /* check all tree entries for artwork, but skip parent link entries */
3353 if (!level_node->parent_link)
3355 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3356 boolean cached = (artwork_new != NULL);
3360 pushTreeInfo(artwork_node, artwork_new);
3364 TreeInfo *topnode_last = *artwork_node;
3365 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3366 ARTWORK_DIRECTORY(type));
3368 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3370 if (topnode_last != *artwork_node) /* check for newly added node */
3372 artwork_new = *artwork_node;
3374 setString(&artwork_new->identifier, level_node->subdir);
3375 setString(&artwork_new->name, level_node->name);
3376 setString(&artwork_new->name_sorting, level_node->name_sorting);
3378 artwork_new->sort_priority = level_node->sort_priority;
3379 artwork_new->color = LEVELCOLOR(artwork_new);
3385 /* insert artwork info (from old cache or filesystem) into new cache */
3386 if (artwork_new != NULL)
3387 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3390 DrawInitText(level_node->name, 150, FC_YELLOW);
3392 if (level_node->node_group != NULL)
3393 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3395 level_node = level_node->next;
3399 void LoadLevelArtworkInfo()
3401 print_timestamp_init("LoadLevelArtworkInfo");
3403 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3405 print_timestamp_time("DrawTimeText");
3407 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3408 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3409 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3410 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3411 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3412 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3414 SaveArtworkInfoCache();
3416 print_timestamp_time("SaveArtworkInfoCache");
3418 /* needed for reloading level artwork not known at ealier stage */
3420 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3422 artwork.gfx_current =
3423 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3424 if (artwork.gfx_current == NULL)
3425 artwork.gfx_current =
3426 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3427 if (artwork.gfx_current == NULL)
3428 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3431 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3433 artwork.snd_current =
3434 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3435 if (artwork.snd_current == NULL)
3436 artwork.snd_current =
3437 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3438 if (artwork.snd_current == NULL)
3439 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3442 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3444 artwork.mus_current =
3445 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3446 if (artwork.mus_current == NULL)
3447 artwork.mus_current =
3448 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3449 if (artwork.mus_current == NULL)
3450 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3453 print_timestamp_time("getTreeInfoFromIdentifier");
3455 sortTreeInfo(&artwork.gfx_first);
3456 sortTreeInfo(&artwork.snd_first);
3457 sortTreeInfo(&artwork.mus_first);
3459 print_timestamp_time("sortTreeInfo");
3461 #if ENABLE_UNUSED_CODE
3462 dumpTreeInfo(artwork.gfx_first, 0);
3463 dumpTreeInfo(artwork.snd_first, 0);
3464 dumpTreeInfo(artwork.mus_first, 0);
3467 print_timestamp_done("LoadLevelArtworkInfo");
3470 static void SaveUserLevelInfo()
3472 LevelDirTree *level_info;
3477 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
3479 if (!(file = fopen(filename, MODE_WRITE)))
3481 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3486 level_info = newTreeInfo();
3488 /* always start with reliable default values */
3489 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3491 setString(&level_info->name, getLoginName());
3492 setString(&level_info->author, getRealName());
3493 level_info->levels = 100;
3494 level_info->first_level = 1;
3496 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3498 fprintFileHeader(file, LEVELINFO_FILENAME);
3501 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3503 if (i == LEVELINFO_TOKEN_NAME ||
3504 i == LEVELINFO_TOKEN_AUTHOR ||
3505 i == LEVELINFO_TOKEN_LEVELS ||
3506 i == LEVELINFO_TOKEN_FIRST_LEVEL)
3507 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3509 /* just to make things nicer :) */
3510 if (i == LEVELINFO_TOKEN_AUTHOR)
3511 fprintf(file, "\n");
3514 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3518 SetFilePermissions(filename, PERMS_PRIVATE);
3520 freeTreeInfo(level_info);
3524 char *getSetupValue(int type, void *value)
3526 static char value_string[MAX_LINE_LEN];
3534 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3538 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3542 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3543 *(int *)value == FALSE ? "off" : "on"));
3547 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3550 case TYPE_YES_NO_AUTO:
3551 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3552 *(int *)value == FALSE ? "no" : "yes"));
3556 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3560 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3564 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3568 sprintf(value_string, "%d", *(int *)value);
3572 if (*(char **)value == NULL)
3575 strcpy(value_string, *(char **)value);
3579 value_string[0] = '\0';
3583 if (type & TYPE_GHOSTED)
3584 strcpy(value_string, "n/a");
3586 return value_string;
3589 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3593 static char token_string[MAX_LINE_LEN];
3594 int token_type = token_info[token_nr].type;
3595 void *setup_value = token_info[token_nr].value;
3596 char *token_text = token_info[token_nr].text;
3597 char *value_string = getSetupValue(token_type, setup_value);
3599 /* build complete token string */
3600 sprintf(token_string, "%s%s", prefix, token_text);
3602 /* build setup entry line */
3603 line = getFormattedSetupEntry(token_string, value_string);
3605 if (token_type == TYPE_KEY_X11)
3607 Key key = *(Key *)setup_value;
3608 char *keyname = getKeyNameFromKey(key);
3610 /* add comment, if useful */
3611 if (!strEqual(keyname, "(undefined)") &&
3612 !strEqual(keyname, "(unknown)"))
3614 /* add at least one whitespace */
3616 for (i = strlen(line); i < token_comment_position; i++)
3620 strcat(line, keyname);
3627 void LoadLevelSetup_LastSeries()
3629 /* ----------------------------------------------------------------------- */
3630 /* ~/.<program>/levelsetup.conf */
3631 /* ----------------------------------------------------------------------- */
3633 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3634 SetupFileHash *level_setup_hash = NULL;
3636 /* always start with reliable default values */
3637 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3639 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3641 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3643 if (leveldir_current == NULL)
3644 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3647 if ((level_setup_hash = loadSetupFileHash(filename)))
3649 char *last_level_series =
3650 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
3652 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3654 if (leveldir_current == NULL)
3655 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3657 freeSetupFileHash(level_setup_hash);
3660 Error(ERR_WARN, "using default setup values");
3665 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
3667 /* ----------------------------------------------------------------------- */
3668 /* ~/.<program>/levelsetup.conf */
3669 /* ----------------------------------------------------------------------- */
3671 // check if the current level directory structure is available at this point
3672 if (leveldir_current == NULL)
3675 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3676 char *level_subdir = leveldir_current->subdir;
3679 InitUserDataDirectory();
3681 if (!(file = fopen(filename, MODE_WRITE)))
3683 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3690 fprintFileHeader(file, LEVELSETUP_FILENAME);
3692 if (deactivate_last_level_series)
3693 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
3695 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
3700 SetFilePermissions(filename, PERMS_PRIVATE);
3705 void SaveLevelSetup_LastSeries()
3707 SaveLevelSetup_LastSeries_Ext(FALSE);
3710 void SaveLevelSetup_LastSeries_Deactivate()
3712 SaveLevelSetup_LastSeries_Ext(TRUE);
3715 static void checkSeriesInfo()
3717 static char *level_directory = NULL;
3720 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
3722 level_directory = getPath2((leveldir_current->in_user_dir ?
3723 getUserLevelDir(NULL) :
3724 options.level_directory),
3725 leveldir_current->fullpath);
3727 if ((dir = openDirectory(level_directory)) == NULL)
3729 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3734 closeDirectory(dir);
3737 void LoadLevelSetup_SeriesInfo()
3740 SetupFileHash *level_setup_hash = NULL;
3741 char *level_subdir = leveldir_current->subdir;
3744 /* always start with reliable default values */
3745 level_nr = leveldir_current->first_level;
3747 for (i = 0; i < MAX_LEVELS; i++)
3749 LevelStats_setPlayed(i, 0);
3750 LevelStats_setSolved(i, 0);
3755 /* ----------------------------------------------------------------------- */
3756 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3757 /* ----------------------------------------------------------------------- */
3759 level_subdir = leveldir_current->subdir;
3761 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3763 if ((level_setup_hash = loadSetupFileHash(filename)))
3767 /* get last played level in this level set */
3769 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
3773 level_nr = atoi(token_value);
3775 if (level_nr < leveldir_current->first_level)
3776 level_nr = leveldir_current->first_level;
3777 if (level_nr > leveldir_current->last_level)
3778 level_nr = leveldir_current->last_level;
3781 /* get handicap level in this level set */
3783 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
3787 int level_nr = atoi(token_value);
3789 if (level_nr < leveldir_current->first_level)
3790 level_nr = leveldir_current->first_level;
3791 if (level_nr > leveldir_current->last_level + 1)
3792 level_nr = leveldir_current->last_level;
3794 if (leveldir_current->user_defined || !leveldir_current->handicap)
3795 level_nr = leveldir_current->last_level;
3797 leveldir_current->handicap_level = level_nr;
3800 /* get number of played and solved levels in this level set */
3802 BEGIN_HASH_ITERATION(level_setup_hash, itr)
3804 char *token = HASH_ITERATION_TOKEN(itr);
3805 char *value = HASH_ITERATION_VALUE(itr);
3807 if (strlen(token) == 3 &&
3808 token[0] >= '0' && token[0] <= '9' &&
3809 token[1] >= '0' && token[1] <= '9' &&
3810 token[2] >= '0' && token[2] <= '9')
3812 int level_nr = atoi(token);
3815 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
3817 value = strchr(value, ' ');
3820 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
3823 END_HASH_ITERATION(hash, itr)
3825 freeSetupFileHash(level_setup_hash);
3828 Error(ERR_WARN, "using default setup values");
3833 void SaveLevelSetup_SeriesInfo()
3836 char *level_subdir = leveldir_current->subdir;
3837 char *level_nr_str = int2str(level_nr, 0);
3838 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
3842 /* ----------------------------------------------------------------------- */
3843 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
3844 /* ----------------------------------------------------------------------- */
3846 InitLevelSetupDirectory(level_subdir);
3848 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
3850 if (!(file = fopen(filename, MODE_WRITE)))
3852 Error(ERR_WARN, "cannot write setup file '%s'", filename);
3857 fprintFileHeader(file, LEVELSETUP_FILENAME);
3859 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
3861 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
3862 handicap_level_str));
3864 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
3867 if (LevelStats_getPlayed(i) > 0 ||
3868 LevelStats_getSolved(i) > 0)
3873 sprintf(token, "%03d", i);
3874 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
3876 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
3882 SetFilePermissions(filename, PERMS_PRIVATE);
3887 int LevelStats_getPlayed(int nr)
3889 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
3892 int LevelStats_getSolved(int nr)
3894 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
3897 void LevelStats_setPlayed(int nr, int value)
3899 if (nr >= 0 && nr < MAX_LEVELS)
3900 level_stats[nr].played = value;
3903 void LevelStats_setSolved(int nr, int value)
3905 if (nr >= 0 && nr < MAX_LEVELS)
3906 level_stats[nr].solved = value;
3909 void LevelStats_incPlayed(int nr)
3911 if (nr >= 0 && nr < MAX_LEVELS)
3912 level_stats[nr].played++;
3915 void LevelStats_incSolved(int nr)
3917 if (nr >= 0 && nr < MAX_LEVELS)
3918 level_stats[nr].solved++;