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 */
34 #define DEBUG_NO_CONFIG_FILE FALSE /* for extra-verbose debug output */
36 #define NUM_LEVELCLASS_DESC 8
38 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
51 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
52 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
53 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
55 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
56 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
57 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
58 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
59 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
62 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
63 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
64 IS_LEVELCLASS_BD(n) ? 2 : \
65 IS_LEVELCLASS_EM(n) ? 3 : \
66 IS_LEVELCLASS_SP(n) ? 4 : \
67 IS_LEVELCLASS_DX(n) ? 5 : \
68 IS_LEVELCLASS_SB(n) ? 6 : \
69 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
70 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
73 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
74 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
75 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
76 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
79 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
80 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
81 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
82 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
85 #define TOKEN_VALUE_POSITION_SHORT 32
86 #define TOKEN_VALUE_POSITION_DEFAULT 40
87 #define TOKEN_COMMENT_POSITION_DEFAULT 60
89 #define MAX_COOKIE_LEN 256
92 static void setTreeInfoToDefaults(TreeInfo *, int);
93 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
94 static int compareTreeInfoEntries(const void *, const void *);
96 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
97 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
99 static SetupFileHash *artworkinfo_cache_old = NULL;
100 static SetupFileHash *artworkinfo_cache_new = NULL;
101 static boolean use_artworkinfo_cache = TRUE;
104 /* ------------------------------------------------------------------------- */
106 /* ------------------------------------------------------------------------- */
108 static char *getLevelClassDescription(TreeInfo *ti)
110 int position = ti->sort_priority / 100;
112 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
113 return levelclass_desc[position];
115 return "Unknown Level Class";
118 static char *getScoreDir(char *level_subdir)
120 static char *score_dir = NULL;
121 static char *score_level_dir = NULL;
122 char *score_subdir = SCORES_DIRECTORY;
124 if (score_dir == NULL)
126 if (program.global_scores)
127 score_dir = getPath2(getCommonDataDir(), score_subdir);
129 score_dir = getPath2(getUserGameDataDir(), score_subdir);
132 if (level_subdir != NULL)
134 checked_free(score_level_dir);
136 score_level_dir = getPath2(score_dir, level_subdir);
138 return score_level_dir;
144 static char *getLevelSetupDir(char *level_subdir)
146 static char *levelsetup_dir = NULL;
147 char *data_dir = getUserGameDataDir();
148 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
150 checked_free(levelsetup_dir);
152 if (level_subdir != NULL)
153 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
155 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
157 return levelsetup_dir;
160 static char *getCacheDir()
162 static char *cache_dir = NULL;
164 if (cache_dir == NULL)
165 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
170 static char *getNetworkDir()
172 static char *network_dir = NULL;
174 if (network_dir == NULL)
175 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
180 static char *getLevelDirFromTreeInfo(TreeInfo *node)
182 static char *level_dir = NULL;
185 return options.level_directory;
187 checked_free(level_dir);
189 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
190 options.level_directory), node->fullpath);
195 char *getUserLevelDir(char *level_subdir)
197 static char *userlevel_dir = NULL;
198 char *data_dir = getUserGameDataDir();
199 char *userlevel_subdir = LEVELS_DIRECTORY;
201 checked_free(userlevel_dir);
203 if (level_subdir != NULL)
204 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
206 userlevel_dir = getPath2(data_dir, userlevel_subdir);
208 return userlevel_dir;
211 char *getNetworkLevelDir(char *level_subdir)
213 static char *network_level_dir = NULL;
214 char *data_dir = getNetworkDir();
215 char *networklevel_subdir = LEVELS_DIRECTORY;
217 checked_free(network_level_dir);
219 if (level_subdir != NULL)
220 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
222 network_level_dir = getPath2(data_dir, networklevel_subdir);
224 return network_level_dir;
227 char *getCurrentLevelDir()
229 return getLevelDirFromTreeInfo(leveldir_current);
232 char *getNewUserLevelSubdir()
234 static char *new_level_subdir = NULL;
235 char *subdir_prefix = getLoginName();
236 char subdir_suffix[10];
237 int max_suffix_number = 1000;
240 while (++i < max_suffix_number)
242 sprintf(subdir_suffix, "_%d", i);
244 checked_free(new_level_subdir);
245 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
247 if (!directoryExists(getUserLevelDir(new_level_subdir)))
251 return new_level_subdir;
254 static char *getTapeDir(char *level_subdir)
256 static char *tape_dir = NULL;
257 char *data_dir = getUserGameDataDir();
258 char *tape_subdir = TAPES_DIRECTORY;
260 checked_free(tape_dir);
262 if (level_subdir != NULL)
263 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
265 tape_dir = getPath2(data_dir, tape_subdir);
270 static char *getSolutionTapeDir()
272 static char *tape_dir = NULL;
273 char *data_dir = getCurrentLevelDir();
274 char *tape_subdir = TAPES_DIRECTORY;
276 checked_free(tape_dir);
278 tape_dir = getPath2(data_dir, tape_subdir);
283 static char *getDefaultGraphicsDir(char *graphics_subdir)
285 static char *graphics_dir = NULL;
287 if (graphics_subdir == NULL)
288 return options.graphics_directory;
290 checked_free(graphics_dir);
292 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
297 static char *getDefaultSoundsDir(char *sounds_subdir)
299 static char *sounds_dir = NULL;
301 if (sounds_subdir == NULL)
302 return options.sounds_directory;
304 checked_free(sounds_dir);
306 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
311 static char *getDefaultMusicDir(char *music_subdir)
313 static char *music_dir = NULL;
315 if (music_subdir == NULL)
316 return options.music_directory;
318 checked_free(music_dir);
320 music_dir = getPath2(options.music_directory, music_subdir);
325 static char *getClassicArtworkSet(int type)
327 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
328 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
329 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
332 static char *getClassicArtworkDir(int type)
334 return (type == TREE_TYPE_GRAPHICS_DIR ?
335 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
336 type == TREE_TYPE_SOUNDS_DIR ?
337 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
338 type == TREE_TYPE_MUSIC_DIR ?
339 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
342 static char *getUserGraphicsDir()
344 static char *usergraphics_dir = NULL;
346 if (usergraphics_dir == NULL)
347 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
349 return usergraphics_dir;
352 static char *getUserSoundsDir()
354 static char *usersounds_dir = NULL;
356 if (usersounds_dir == NULL)
357 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
359 return usersounds_dir;
362 static char *getUserMusicDir()
364 static char *usermusic_dir = NULL;
366 if (usermusic_dir == NULL)
367 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
369 return usermusic_dir;
372 static char *getSetupArtworkDir(TreeInfo *ti)
374 static char *artwork_dir = NULL;
379 checked_free(artwork_dir);
381 artwork_dir = getPath2(ti->basepath, ti->fullpath);
386 char *setLevelArtworkDir(TreeInfo *ti)
388 char **artwork_path_ptr, **artwork_set_ptr;
389 TreeInfo *level_artwork;
391 if (ti == NULL || leveldir_current == NULL)
394 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
395 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
397 checked_free(*artwork_path_ptr);
399 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
401 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
406 No (or non-existing) artwork configured in "levelinfo.conf". This would
407 normally result in using the artwork configured in the setup menu. But
408 if an artwork subdirectory exists (which might contain custom artwork
409 or an artwork configuration file), this level artwork must be treated
410 as relative to the default "classic" artwork, not to the artwork that
411 is currently configured in the setup menu.
413 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
414 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
415 the real "classic" artwork from the original R'n'D (like "gfx_classic").
418 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
420 checked_free(*artwork_set_ptr);
422 if (directoryExists(dir))
424 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
425 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
429 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
430 *artwork_set_ptr = NULL;
436 return *artwork_set_ptr;
439 inline static char *getLevelArtworkSet(int type)
441 if (leveldir_current == NULL)
444 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
447 inline static char *getLevelArtworkDir(int type)
449 if (leveldir_current == NULL)
450 return UNDEFINED_FILENAME;
452 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
455 char *getProgramMainDataPath(char *command_filename, char *base_path)
457 /* check if the program's main data base directory is configured */
458 if (!strEqual(base_path, "."))
461 /* if the program is configured to start from current directory (default),
462 determine program package directory from program binary (some versions
463 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
464 set the current working directory to the program package directory) */
465 char *main_data_path = getBasePath(command_filename);
467 #if defined(PLATFORM_MACOSX)
468 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
470 char *main_data_path_old = main_data_path;
472 // cut relative path to Mac OS X application binary directory from path
473 main_data_path[strlen(main_data_path) -
474 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
476 // cut trailing path separator from path (but not if path is root directory)
477 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
478 main_data_path[strlen(main_data_path) - 1] = '\0';
480 // replace empty path with current directory
481 if (strEqual(main_data_path, ""))
482 main_data_path = ".";
484 // add relative path to Mac OS X application resources directory to path
485 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
487 free(main_data_path_old);
491 return main_data_path;
494 char *getProgramConfigFilename(char *command_filename)
496 char *command_filename_1 = getStringCopy(command_filename);
498 // strip trailing executable suffix from command filename
499 if (strSuffix(command_filename_1, ".exe"))
500 command_filename_1[strlen(command_filename_1) - 4] = '\0';
502 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
503 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
505 char *command_basepath = getBasePath(command_filename);
506 char *command_basename = getBaseNameNoSuffix(command_filename);
507 char *command_filename_2 = getPath2(command_basepath, command_basename);
509 char *config_filename_1 = getStringCat2(command_filename_1, ".conf");
510 char *config_filename_2 = getStringCat2(command_filename_2, ".conf");
511 char *config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
513 // 1st try: look for config file that exactly matches the binary filename
514 if (fileExists(config_filename_1))
515 return config_filename_1;
517 // 2nd try: look for config file that matches binary filename without suffix
518 if (fileExists(config_filename_2))
519 return config_filename_2;
521 // 3rd try: return setup config filename in global program config directory
522 return config_filename_3;
525 char *getTapeFilename(int nr)
527 static char *filename = NULL;
528 char basename[MAX_FILENAME_LEN];
530 checked_free(filename);
532 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
533 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
538 char *getSolutionTapeFilename(int nr)
540 static char *filename = NULL;
541 char basename[MAX_FILENAME_LEN];
543 checked_free(filename);
545 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
546 filename = getPath2(getSolutionTapeDir(), basename);
548 if (!fileExists(filename))
550 static char *filename_sln = NULL;
552 checked_free(filename_sln);
554 sprintf(basename, "%03d.sln", nr);
555 filename_sln = getPath2(getSolutionTapeDir(), basename);
557 if (fileExists(filename_sln))
564 char *getScoreFilename(int nr)
566 static char *filename = NULL;
567 char basename[MAX_FILENAME_LEN];
569 checked_free(filename);
571 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
573 /* used instead of "leveldir_current->subdir" (for network games) */
574 filename = getPath2(getScoreDir(levelset.identifier), basename);
579 char *getSetupFilename()
581 static char *filename = NULL;
583 checked_free(filename);
585 filename = getPath2(getSetupDir(), SETUP_FILENAME);
590 char *getDefaultSetupFilename()
592 return program.config_filename;
595 char *getEditorSetupFilename()
597 static char *filename = NULL;
599 checked_free(filename);
600 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
602 if (fileExists(filename))
605 checked_free(filename);
606 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
611 char *getHelpAnimFilename()
613 static char *filename = NULL;
615 checked_free(filename);
617 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
622 char *getHelpTextFilename()
624 static char *filename = NULL;
626 checked_free(filename);
628 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
633 char *getLevelSetInfoFilename()
635 static char *filename = NULL;
650 for (i = 0; basenames[i] != NULL; i++)
652 checked_free(filename);
653 filename = getPath2(getCurrentLevelDir(), basenames[i]);
655 if (fileExists(filename))
662 char *getLevelSetTitleMessageBasename(int nr, boolean initial)
664 static char basename[32];
666 sprintf(basename, "%s_%d.txt",
667 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
672 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
674 static char *filename = NULL;
676 boolean skip_setup_artwork = FALSE;
678 checked_free(filename);
680 basename = getLevelSetTitleMessageBasename(nr, initial);
682 if (!gfx.override_level_graphics)
684 /* 1st try: look for special artwork in current level series directory */
685 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
686 if (fileExists(filename))
691 /* 2nd try: look for message file in current level set directory */
692 filename = getPath2(getCurrentLevelDir(), basename);
693 if (fileExists(filename))
698 /* check if there is special artwork configured in level series config */
699 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
701 /* 3rd try: look for special artwork configured in level series config */
702 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
703 if (fileExists(filename))
708 /* take missing artwork configured in level set config from default */
709 skip_setup_artwork = TRUE;
713 if (!skip_setup_artwork)
715 /* 4th try: look for special artwork in configured artwork directory */
716 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
717 if (fileExists(filename))
723 /* 5th try: look for default artwork in new default artwork directory */
724 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
725 if (fileExists(filename))
730 /* 6th try: look for default artwork in old default artwork directory */
731 filename = getPath2(options.graphics_directory, basename);
732 if (fileExists(filename))
735 return NULL; /* cannot find specified artwork file anywhere */
738 static char *getCorrectedArtworkBasename(char *basename)
743 char *getCustomImageFilename(char *basename)
745 static char *filename = NULL;
746 boolean skip_setup_artwork = FALSE;
748 checked_free(filename);
750 basename = getCorrectedArtworkBasename(basename);
752 if (!gfx.override_level_graphics)
754 /* 1st try: look for special artwork in current level series directory */
755 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
756 if (fileExists(filename))
761 /* check if there is special artwork configured in level series config */
762 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
764 /* 2nd try: look for special artwork configured in level series config */
765 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
766 if (fileExists(filename))
771 /* take missing artwork configured in level set config from default */
772 skip_setup_artwork = TRUE;
776 if (!skip_setup_artwork)
778 /* 3rd try: look for special artwork in configured artwork directory */
779 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
780 if (fileExists(filename))
786 /* 4th try: look for default artwork in new default artwork directory */
787 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
788 if (fileExists(filename))
793 /* 5th try: look for default artwork in old default artwork directory */
794 filename = getImg2(options.graphics_directory, basename);
795 if (fileExists(filename))
798 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
803 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
806 /* 6th try: look for fallback artwork in old default artwork directory */
807 /* (needed to prevent errors when trying to access unused artwork files) */
808 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
809 if (fileExists(filename))
813 return NULL; /* cannot find specified artwork file anywhere */
816 char *getCustomSoundFilename(char *basename)
818 static char *filename = NULL;
819 boolean skip_setup_artwork = FALSE;
821 checked_free(filename);
823 basename = getCorrectedArtworkBasename(basename);
825 if (!gfx.override_level_sounds)
827 /* 1st try: look for special artwork in current level series directory */
828 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
829 if (fileExists(filename))
834 /* check if there is special artwork configured in level series config */
835 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
837 /* 2nd try: look for special artwork configured in level series config */
838 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
839 if (fileExists(filename))
844 /* take missing artwork configured in level set config from default */
845 skip_setup_artwork = TRUE;
849 if (!skip_setup_artwork)
851 /* 3rd try: look for special artwork in configured artwork directory */
852 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
853 if (fileExists(filename))
859 /* 4th try: look for default artwork in new default artwork directory */
860 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
861 if (fileExists(filename))
866 /* 5th try: look for default artwork in old default artwork directory */
867 filename = getPath2(options.sounds_directory, basename);
868 if (fileExists(filename))
871 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
876 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
879 /* 6th try: look for fallback artwork in old default artwork directory */
880 /* (needed to prevent errors when trying to access unused artwork files) */
881 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
882 if (fileExists(filename))
886 return NULL; /* cannot find specified artwork file anywhere */
889 char *getCustomMusicFilename(char *basename)
891 static char *filename = NULL;
892 boolean skip_setup_artwork = FALSE;
894 checked_free(filename);
896 basename = getCorrectedArtworkBasename(basename);
898 if (!gfx.override_level_music)
900 /* 1st try: look for special artwork in current level series directory */
901 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
902 if (fileExists(filename))
907 /* check if there is special artwork configured in level series config */
908 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
910 /* 2nd try: look for special artwork configured in level series config */
911 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
912 if (fileExists(filename))
917 /* take missing artwork configured in level set config from default */
918 skip_setup_artwork = TRUE;
922 if (!skip_setup_artwork)
924 /* 3rd try: look for special artwork in configured artwork directory */
925 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
926 if (fileExists(filename))
932 /* 4th try: look for default artwork in new default artwork directory */
933 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
934 if (fileExists(filename))
939 /* 5th try: look for default artwork in old default artwork directory */
940 filename = getPath2(options.music_directory, basename);
941 if (fileExists(filename))
944 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
949 Error(ERR_WARN, "cannot find artwork file '%s' (using fallback)",
952 /* 6th try: look for fallback artwork in old default artwork directory */
953 /* (needed to prevent errors when trying to access unused artwork files) */
954 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
955 if (fileExists(filename))
959 return NULL; /* cannot find specified artwork file anywhere */
962 char *getCustomArtworkFilename(char *basename, int type)
964 if (type == ARTWORK_TYPE_GRAPHICS)
965 return getCustomImageFilename(basename);
966 else if (type == ARTWORK_TYPE_SOUNDS)
967 return getCustomSoundFilename(basename);
968 else if (type == ARTWORK_TYPE_MUSIC)
969 return getCustomMusicFilename(basename);
971 return UNDEFINED_FILENAME;
974 char *getCustomArtworkConfigFilename(int type)
976 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
979 char *getCustomArtworkLevelConfigFilename(int type)
981 static char *filename = NULL;
983 checked_free(filename);
985 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
990 char *getCustomMusicDirectory(void)
992 static char *directory = NULL;
993 boolean skip_setup_artwork = FALSE;
995 checked_free(directory);
997 if (!gfx.override_level_music)
999 /* 1st try: look for special artwork in current level series directory */
1000 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1001 if (directoryExists(directory))
1006 /* check if there is special artwork configured in level series config */
1007 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1009 /* 2nd try: look for special artwork configured in level series config */
1010 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1011 if (directoryExists(directory))
1016 /* take missing artwork configured in level set config from default */
1017 skip_setup_artwork = TRUE;
1021 if (!skip_setup_artwork)
1023 /* 3rd try: look for special artwork in configured artwork directory */
1024 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1025 if (directoryExists(directory))
1031 /* 4th try: look for default artwork in new default artwork directory */
1032 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1033 if (directoryExists(directory))
1038 /* 5th try: look for default artwork in old default artwork directory */
1039 directory = getStringCopy(options.music_directory);
1040 if (directoryExists(directory))
1043 return NULL; /* cannot find specified artwork file anywhere */
1046 void InitTapeDirectory(char *level_subdir)
1048 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1049 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1050 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1053 void InitScoreDirectory(char *level_subdir)
1055 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1057 if (program.global_scores)
1058 createDirectory(getCommonDataDir(), "common data", permissions);
1060 createDirectory(getUserGameDataDir(), "user data", permissions);
1062 createDirectory(getScoreDir(NULL), "main score", permissions);
1063 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1066 static void SaveUserLevelInfo();
1068 void InitUserLevelDirectory(char *level_subdir)
1070 if (!directoryExists(getUserLevelDir(level_subdir)))
1072 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1073 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1074 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1076 SaveUserLevelInfo();
1080 void InitNetworkLevelDirectory(char *level_subdir)
1082 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1084 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1085 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1086 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1087 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1091 void InitLevelSetupDirectory(char *level_subdir)
1093 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1094 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1095 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1098 void InitCacheDirectory()
1100 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1101 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1105 /* ------------------------------------------------------------------------- */
1106 /* some functions to handle lists of level and artwork directories */
1107 /* ------------------------------------------------------------------------- */
1109 TreeInfo *newTreeInfo()
1111 return checked_calloc(sizeof(TreeInfo));
1114 TreeInfo *newTreeInfo_setDefaults(int type)
1116 TreeInfo *ti = newTreeInfo();
1118 setTreeInfoToDefaults(ti, type);
1123 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1125 node_new->next = *node_first;
1126 *node_first = node_new;
1129 int numTreeInfo(TreeInfo *node)
1142 boolean validLevelSeries(TreeInfo *node)
1144 return (node != NULL && !node->node_group && !node->parent_link);
1147 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1152 if (node->node_group) /* enter level group (step down into tree) */
1153 return getFirstValidTreeInfoEntry(node->node_group);
1154 else if (node->parent_link) /* skip start entry of level group */
1156 if (node->next) /* get first real level series entry */
1157 return getFirstValidTreeInfoEntry(node->next);
1158 else /* leave empty level group and go on */
1159 return getFirstValidTreeInfoEntry(node->node_parent->next);
1161 else /* this seems to be a regular level series */
1165 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1170 if (node->node_parent == NULL) /* top level group */
1171 return *node->node_top;
1172 else /* sub level group */
1173 return node->node_parent->node_group;
1176 int numTreeInfoInGroup(TreeInfo *node)
1178 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1181 int posTreeInfo(TreeInfo *node)
1183 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1188 if (node_cmp == node)
1192 node_cmp = node_cmp->next;
1198 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1200 TreeInfo *node_default = node;
1212 return node_default;
1215 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1217 if (identifier == NULL)
1222 if (node->node_group)
1224 TreeInfo *node_group;
1226 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1231 else if (!node->parent_link)
1233 if (strEqual(identifier, node->identifier))
1243 TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1244 TreeInfo *node, boolean skip_sets_without_levels)
1251 if (!node->parent_link && !node->level_group &&
1252 skip_sets_without_levels && node->levels == 0)
1253 return cloneTreeNode(node_top, node_parent, node->next,
1254 skip_sets_without_levels);
1256 node_new = getTreeInfoCopy(node); /* copy complete node */
1258 node_new->node_top = node_top; /* correct top node link */
1259 node_new->node_parent = node_parent; /* correct parent node link */
1261 if (node->level_group)
1262 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1263 skip_sets_without_levels);
1265 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1266 skip_sets_without_levels);
1271 void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1273 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1275 *ti_new = ti_cloned;
1278 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1280 boolean settings_changed = FALSE;
1284 if (node->graphics_set_ecs && !setup.prefer_aga_graphics &&
1285 !strEqual(node->graphics_set, node->graphics_set_ecs))
1287 setString(&node->graphics_set, node->graphics_set_ecs);
1288 settings_changed = TRUE;
1290 else if (node->graphics_set_aga && setup.prefer_aga_graphics &&
1291 !strEqual(node->graphics_set, node->graphics_set_aga))
1293 setString(&node->graphics_set, node->graphics_set_aga);
1294 settings_changed = TRUE;
1297 if (node->node_group != NULL)
1298 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1303 return settings_changed;
1306 void dumpTreeInfo(TreeInfo *node, int depth)
1310 printf("Dumping TreeInfo:\n");
1314 for (i = 0; i < (depth + 1) * 3; i++)
1317 printf("'%s' / '%s'\n", node->identifier, node->name);
1320 // use for dumping artwork info tree
1321 printf("subdir == '%s' ['%s', '%s'] [%d])\n",
1322 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1325 if (node->node_group != NULL)
1326 dumpTreeInfo(node->node_group, depth + 1);
1332 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1333 int (*compare_function)(const void *,
1336 int num_nodes = numTreeInfo(*node_first);
1337 TreeInfo **sort_array;
1338 TreeInfo *node = *node_first;
1344 /* allocate array for sorting structure pointers */
1345 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1347 /* writing structure pointers to sorting array */
1348 while (i < num_nodes && node) /* double boundary check... */
1350 sort_array[i] = node;
1356 /* sorting the structure pointers in the sorting array */
1357 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1360 /* update the linkage of list elements with the sorted node array */
1361 for (i = 0; i < num_nodes - 1; i++)
1362 sort_array[i]->next = sort_array[i + 1];
1363 sort_array[num_nodes - 1]->next = NULL;
1365 /* update the linkage of the main list anchor pointer */
1366 *node_first = sort_array[0];
1370 /* now recursively sort the level group structures */
1374 if (node->node_group != NULL)
1375 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1381 void sortTreeInfo(TreeInfo **node_first)
1383 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1387 /* ========================================================================= */
1388 /* some stuff from "files.c" */
1389 /* ========================================================================= */
1391 #if defined(PLATFORM_WIN32)
1393 #define S_IRGRP S_IRUSR
1396 #define S_IROTH S_IRUSR
1399 #define S_IWGRP S_IWUSR
1402 #define S_IWOTH S_IWUSR
1405 #define S_IXGRP S_IXUSR
1408 #define S_IXOTH S_IXUSR
1411 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1416 #endif /* PLATFORM_WIN32 */
1418 /* file permissions for newly written files */
1419 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1420 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1421 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1423 #define MODE_W_PRIVATE (S_IWUSR)
1424 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1425 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1427 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1428 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1429 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1431 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1432 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1433 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1438 static char *dir = NULL;
1440 #if defined(PLATFORM_WIN32)
1443 dir = checked_malloc(MAX_PATH + 1);
1445 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1448 #elif defined(PLATFORM_UNIX)
1451 if ((dir = getenv("HOME")) == NULL)
1455 if ((pwd = getpwuid(getuid())) != NULL)
1456 dir = getStringCopy(pwd->pw_dir);
1468 char *getCommonDataDir(void)
1470 static char *common_data_dir = NULL;
1472 #if defined(PLATFORM_WIN32)
1473 if (common_data_dir == NULL)
1475 char *dir = checked_malloc(MAX_PATH + 1);
1477 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1478 && !strEqual(dir, "")) /* empty for Windows 95/98 */
1479 common_data_dir = getPath2(dir, program.userdata_subdir);
1481 common_data_dir = options.rw_base_directory;
1484 if (common_data_dir == NULL)
1485 common_data_dir = options.rw_base_directory;
1488 return common_data_dir;
1491 char *getPersonalDataDir(void)
1493 static char *personal_data_dir = NULL;
1495 #if defined(PLATFORM_MACOSX)
1496 if (personal_data_dir == NULL)
1497 personal_data_dir = getPath2(getHomeDir(), "Documents");
1499 if (personal_data_dir == NULL)
1500 personal_data_dir = getHomeDir();
1503 return personal_data_dir;
1506 char *getUserGameDataDir(void)
1508 static char *user_game_data_dir = NULL;
1510 #if defined(PLATFORM_ANDROID)
1511 if (user_game_data_dir == NULL)
1512 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1513 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1514 SDL_AndroidGetExternalStoragePath() :
1515 SDL_AndroidGetInternalStoragePath());
1517 if (user_game_data_dir == NULL)
1518 user_game_data_dir = getPath2(getPersonalDataDir(),
1519 program.userdata_subdir);
1522 return user_game_data_dir;
1527 return getUserGameDataDir();
1530 static mode_t posix_umask(mode_t mask)
1532 #if defined(PLATFORM_UNIX)
1539 static int posix_mkdir(const char *pathname, mode_t mode)
1541 #if defined(PLATFORM_WIN32)
1542 return mkdir(pathname);
1544 return mkdir(pathname, mode);
1548 static boolean posix_process_running_setgid()
1550 #if defined(PLATFORM_UNIX)
1551 return (getgid() != getegid());
1557 void createDirectory(char *dir, char *text, int permission_class)
1559 if (directoryExists(dir))
1562 /* leave "other" permissions in umask untouched, but ensure group parts
1563 of USERDATA_DIR_MODE are not masked */
1564 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1565 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1566 mode_t last_umask = posix_umask(0);
1567 mode_t group_umask = ~(dir_mode & S_IRWXG);
1568 int running_setgid = posix_process_running_setgid();
1570 if (permission_class == PERMS_PUBLIC)
1572 /* if we're setgid, protect files against "other" */
1573 /* else keep umask(0) to make the dir world-writable */
1576 posix_umask(last_umask & group_umask);
1578 dir_mode = DIR_PERMS_PUBLIC_ALL;
1581 if (posix_mkdir(dir, dir_mode) != 0)
1582 Error(ERR_WARN, "cannot create %s directory '%s': %s",
1583 text, dir, strerror(errno));
1585 if (permission_class == PERMS_PUBLIC && !running_setgid)
1586 chmod(dir, dir_mode);
1588 posix_umask(last_umask); /* restore previous umask */
1591 void InitUserDataDirectory()
1593 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1596 void SetFilePermissions(char *filename, int permission_class)
1598 int running_setgid = posix_process_running_setgid();
1599 int perms = (permission_class == PERMS_PRIVATE ?
1600 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1602 if (permission_class == PERMS_PUBLIC && !running_setgid)
1603 perms = FILE_PERMS_PUBLIC_ALL;
1605 chmod(filename, perms);
1608 char *getCookie(char *file_type)
1610 static char cookie[MAX_COOKIE_LEN + 1];
1612 if (strlen(program.cookie_prefix) + 1 +
1613 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1614 return "[COOKIE ERROR]"; /* should never happen */
1616 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1617 program.cookie_prefix, file_type,
1618 program.version_super, program.version_major);
1623 void fprintFileHeader(FILE *file, char *basename)
1625 char *prefix = "# ";
1628 fprintf_line_with_prefix(file, prefix, sep1, 77);
1629 fprintf(file, "%s%s\n", prefix, basename);
1630 fprintf_line_with_prefix(file, prefix, sep1, 77);
1631 fprintf(file, "\n");
1634 int getFileVersionFromCookieString(const char *cookie)
1636 const char *ptr_cookie1, *ptr_cookie2;
1637 const char *pattern1 = "_FILE_VERSION_";
1638 const char *pattern2 = "?.?";
1639 const int len_cookie = strlen(cookie);
1640 const int len_pattern1 = strlen(pattern1);
1641 const int len_pattern2 = strlen(pattern2);
1642 const int len_pattern = len_pattern1 + len_pattern2;
1643 int version_super, version_major;
1645 if (len_cookie <= len_pattern)
1648 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1649 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1651 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1654 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1655 ptr_cookie2[1] != '.' ||
1656 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1659 version_super = ptr_cookie2[0] - '0';
1660 version_major = ptr_cookie2[2] - '0';
1662 return VERSION_IDENT(version_super, version_major, 0, 0);
1665 boolean checkCookieString(const char *cookie, const char *template)
1667 const char *pattern = "_FILE_VERSION_?.?";
1668 const int len_cookie = strlen(cookie);
1669 const int len_template = strlen(template);
1670 const int len_pattern = strlen(pattern);
1672 if (len_cookie != len_template)
1675 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1682 /* ------------------------------------------------------------------------- */
1683 /* setup file list and hash handling functions */
1684 /* ------------------------------------------------------------------------- */
1686 char *getFormattedSetupEntry(char *token, char *value)
1689 static char entry[MAX_LINE_LEN];
1691 /* if value is an empty string, just return token without value */
1695 /* start with the token and some spaces to format output line */
1696 sprintf(entry, "%s:", token);
1697 for (i = strlen(entry); i < token_value_position; i++)
1700 /* continue with the token's value */
1701 strcat(entry, value);
1706 SetupFileList *newSetupFileList(char *token, char *value)
1708 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1710 new->token = getStringCopy(token);
1711 new->value = getStringCopy(value);
1718 void freeSetupFileList(SetupFileList *list)
1723 checked_free(list->token);
1724 checked_free(list->value);
1727 freeSetupFileList(list->next);
1732 char *getListEntry(SetupFileList *list, char *token)
1737 if (strEqual(list->token, token))
1740 return getListEntry(list->next, token);
1743 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1748 if (strEqual(list->token, token))
1750 checked_free(list->value);
1752 list->value = getStringCopy(value);
1756 else if (list->next == NULL)
1757 return (list->next = newSetupFileList(token, value));
1759 return setListEntry(list->next, token, value);
1762 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1767 if (list->next == NULL)
1768 return (list->next = newSetupFileList(token, value));
1770 return addListEntry(list->next, token, value);
1773 #if ENABLE_UNUSED_CODE
1775 static void printSetupFileList(SetupFileList *list)
1780 printf("token: '%s'\n", list->token);
1781 printf("value: '%s'\n", list->value);
1783 printSetupFileList(list->next);
1789 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1790 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1791 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1792 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1794 #define insert_hash_entry hashtable_insert
1795 #define search_hash_entry hashtable_search
1796 #define change_hash_entry hashtable_change
1797 #define remove_hash_entry hashtable_remove
1800 unsigned int get_hash_from_key(void *key)
1805 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1806 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1807 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1808 it works better than many other constants, prime or not) has never been
1809 adequately explained.
1811 If you just want to have a good hash function, and cannot wait, djb2
1812 is one of the best string hash functions i know. It has excellent
1813 distribution and speed on many different sets of keys and table sizes.
1814 You are not likely to do better with one of the "well known" functions
1815 such as PJW, K&R, etc.
1817 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1820 char *str = (char *)key;
1821 unsigned int hash = 5381;
1824 while ((c = *str++))
1825 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1830 static int keys_are_equal(void *key1, void *key2)
1832 return (strEqual((char *)key1, (char *)key2));
1835 SetupFileHash *newSetupFileHash()
1837 SetupFileHash *new_hash =
1838 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1840 if (new_hash == NULL)
1841 Error(ERR_EXIT, "create_hashtable() failed -- out of memory");
1846 void freeSetupFileHash(SetupFileHash *hash)
1851 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1854 char *getHashEntry(SetupFileHash *hash, char *token)
1859 return search_hash_entry(hash, token);
1862 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1869 value_copy = getStringCopy(value);
1871 /* change value; if it does not exist, insert it as new */
1872 if (!change_hash_entry(hash, token, value_copy))
1873 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1874 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1877 char *removeHashEntry(SetupFileHash *hash, char *token)
1882 return remove_hash_entry(hash, token);
1885 #if ENABLE_UNUSED_CODE
1887 static void printSetupFileHash(SetupFileHash *hash)
1889 BEGIN_HASH_ITERATION(hash, itr)
1891 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1892 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1894 END_HASH_ITERATION(hash, itr)
1899 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1900 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1901 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1903 static boolean token_value_separator_found = FALSE;
1904 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1905 static boolean token_value_separator_warning = FALSE;
1907 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1908 static boolean token_already_exists_warning = FALSE;
1911 static boolean getTokenValueFromSetupLineExt(char *line,
1912 char **token_ptr, char **value_ptr,
1913 char *filename, char *line_raw,
1915 boolean separator_required)
1917 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1918 char *token, *value, *line_ptr;
1920 /* when externally invoked via ReadTokenValueFromLine(), copy line buffers */
1921 if (line_raw == NULL)
1923 strncpy(line_copy, line, MAX_LINE_LEN);
1924 line_copy[MAX_LINE_LEN] = '\0';
1927 strcpy(line_raw_copy, line_copy);
1928 line_raw = line_raw_copy;
1931 /* cut trailing comment from input line */
1932 for (line_ptr = line; *line_ptr; line_ptr++)
1934 if (*line_ptr == '#')
1941 /* cut trailing whitespaces from input line */
1942 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1943 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1946 /* ignore empty lines */
1950 /* cut leading whitespaces from token */
1951 for (token = line; *token; token++)
1952 if (*token != ' ' && *token != '\t')
1955 /* start with empty value as reliable default */
1958 token_value_separator_found = FALSE;
1960 /* find end of token to determine start of value */
1961 for (line_ptr = token; *line_ptr; line_ptr++)
1963 /* first look for an explicit token/value separator, like ':' or '=' */
1964 if (*line_ptr == ':' || *line_ptr == '=')
1966 *line_ptr = '\0'; /* terminate token string */
1967 value = line_ptr + 1; /* set beginning of value */
1969 token_value_separator_found = TRUE;
1975 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
1976 /* fallback: if no token/value separator found, also allow whitespaces */
1977 if (!token_value_separator_found && !separator_required)
1979 for (line_ptr = token; *line_ptr; line_ptr++)
1981 if (*line_ptr == ' ' || *line_ptr == '\t')
1983 *line_ptr = '\0'; /* terminate token string */
1984 value = line_ptr + 1; /* set beginning of value */
1986 token_value_separator_found = TRUE;
1992 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1993 if (token_value_separator_found)
1995 if (!token_value_separator_warning)
1997 Error(ERR_INFO_LINE, "-");
1999 if (filename != NULL)
2001 Error(ERR_WARN, "missing token/value separator(s) in config file:");
2002 Error(ERR_INFO, "- config file: '%s'", filename);
2006 Error(ERR_WARN, "missing token/value separator(s):");
2009 token_value_separator_warning = TRUE;
2012 if (filename != NULL)
2013 Error(ERR_INFO, "- line %d: '%s'", line_nr, line_raw);
2015 Error(ERR_INFO, "- line: '%s'", line_raw);
2021 /* cut trailing whitespaces from token */
2022 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2023 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2026 /* cut leading whitespaces from value */
2027 for (; *value; value++)
2028 if (*value != ' ' && *value != '\t')
2037 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2039 /* while the internal (old) interface does not require a token/value
2040 separator (for downwards compatibility with existing files which
2041 don't use them), it is mandatory for the external (new) interface */
2043 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2046 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2047 boolean top_recursion_level, boolean is_hash)
2049 static SetupFileHash *include_filename_hash = NULL;
2050 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2051 char *token, *value, *line_ptr;
2052 void *insert_ptr = NULL;
2053 boolean read_continued_line = FALSE;
2055 int line_nr = 0, token_count = 0, include_count = 0;
2057 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2058 token_value_separator_warning = FALSE;
2061 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2062 token_already_exists_warning = FALSE;
2065 if (!(file = openFile(filename, MODE_READ)))
2067 #if DEBUG_NO_CONFIG_FILE
2068 Error(ERR_DEBUG, "cannot open configuration file '%s'", filename);
2074 /* use "insert pointer" to store list end for constant insertion complexity */
2076 insert_ptr = setup_file_data;
2078 /* on top invocation, create hash to mark included files (to prevent loops) */
2079 if (top_recursion_level)
2080 include_filename_hash = newSetupFileHash();
2082 /* mark this file as already included (to prevent including it again) */
2083 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2085 while (!checkEndOfFile(file))
2087 /* read next line of input file */
2088 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2091 /* check if line was completely read and is terminated by line break */
2092 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2095 /* cut trailing line break (this can be newline and/or carriage return) */
2096 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2097 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2100 /* copy raw input line for later use (mainly debugging output) */
2101 strcpy(line_raw, line);
2103 if (read_continued_line)
2105 /* append new line to existing line, if there is enough space */
2106 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2107 strcat(previous_line, line_ptr);
2109 strcpy(line, previous_line); /* copy storage buffer to line */
2111 read_continued_line = FALSE;
2114 /* if the last character is '\', continue at next line */
2115 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2117 line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */
2118 strcpy(previous_line, line); /* copy line to storage buffer */
2120 read_continued_line = TRUE;
2125 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2126 line_raw, line_nr, FALSE))
2131 if (strEqual(token, "include"))
2133 if (getHashEntry(include_filename_hash, value) == NULL)
2135 char *basepath = getBasePath(filename);
2136 char *basename = getBaseName(value);
2137 char *filename_include = getPath2(basepath, basename);
2139 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2143 free(filename_include);
2149 Error(ERR_WARN, "ignoring already processed file '%s'", value);
2156 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2158 getHashEntry((SetupFileHash *)setup_file_data, token);
2160 if (old_value != NULL)
2162 if (!token_already_exists_warning)
2164 Error(ERR_INFO_LINE, "-");
2165 Error(ERR_WARN, "duplicate token(s) found in config file:");
2166 Error(ERR_INFO, "- config file: '%s'", filename);
2168 token_already_exists_warning = TRUE;
2171 Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr);
2172 Error(ERR_INFO, " old value: '%s'", old_value);
2173 Error(ERR_INFO, " new value: '%s'", value);
2177 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2181 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2191 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2192 if (token_value_separator_warning)
2193 Error(ERR_INFO_LINE, "-");
2196 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2197 if (token_already_exists_warning)
2198 Error(ERR_INFO_LINE, "-");
2201 if (token_count == 0 && include_count == 0)
2202 Error(ERR_WARN, "configuration file '%s' is empty", filename);
2204 if (top_recursion_level)
2205 freeSetupFileHash(include_filename_hash);
2210 void saveSetupFileHash(SetupFileHash *hash, char *filename)
2214 if (!(file = fopen(filename, MODE_WRITE)))
2216 Error(ERR_WARN, "cannot write configuration file '%s'", filename);
2221 BEGIN_HASH_ITERATION(hash, itr)
2223 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2224 HASH_ITERATION_VALUE(itr)));
2226 END_HASH_ITERATION(hash, itr)
2231 SetupFileList *loadSetupFileList(char *filename)
2233 SetupFileList *setup_file_list = newSetupFileList("", "");
2234 SetupFileList *first_valid_list_entry;
2236 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2238 freeSetupFileList(setup_file_list);
2243 first_valid_list_entry = setup_file_list->next;
2245 /* free empty list header */
2246 setup_file_list->next = NULL;
2247 freeSetupFileList(setup_file_list);
2249 return first_valid_list_entry;
2252 SetupFileHash *loadSetupFileHash(char *filename)
2254 SetupFileHash *setup_file_hash = newSetupFileHash();
2256 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2258 freeSetupFileHash(setup_file_hash);
2263 return setup_file_hash;
2267 /* ========================================================================= */
2268 /* setup file stuff */
2269 /* ========================================================================= */
2271 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2272 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2273 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2275 /* level directory info */
2276 #define LEVELINFO_TOKEN_IDENTIFIER 0
2277 #define LEVELINFO_TOKEN_NAME 1
2278 #define LEVELINFO_TOKEN_NAME_SORTING 2
2279 #define LEVELINFO_TOKEN_AUTHOR 3
2280 #define LEVELINFO_TOKEN_YEAR 4
2281 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2282 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2283 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2284 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2285 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2286 #define LEVELINFO_TOKEN_TESTED_BY 10
2287 #define LEVELINFO_TOKEN_LEVELS 11
2288 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2289 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2290 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2291 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2292 #define LEVELINFO_TOKEN_READONLY 16
2293 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2294 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2295 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2296 #define LEVELINFO_TOKEN_SOUNDS_SET 20
2297 #define LEVELINFO_TOKEN_MUSIC_SET 21
2298 #define LEVELINFO_TOKEN_FILENAME 22
2299 #define LEVELINFO_TOKEN_FILETYPE 23
2300 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 24
2301 #define LEVELINFO_TOKEN_HANDICAP 25
2302 #define LEVELINFO_TOKEN_SKIP_LEVELS 26
2304 #define NUM_LEVELINFO_TOKENS 27
2306 static LevelDirTree ldi;
2308 static struct TokenInfo levelinfo_tokens[] =
2310 /* level directory info */
2311 { TYPE_STRING, &ldi.identifier, "identifier" },
2312 { TYPE_STRING, &ldi.name, "name" },
2313 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2314 { TYPE_STRING, &ldi.author, "author" },
2315 { TYPE_STRING, &ldi.year, "year" },
2316 { TYPE_STRING, &ldi.program_title, "program_title" },
2317 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2318 { TYPE_STRING, &ldi.program_company, "program_company" },
2319 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2320 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2321 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2322 { TYPE_INTEGER, &ldi.levels, "levels" },
2323 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2324 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2325 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2326 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2327 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2328 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2329 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2330 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2331 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2332 { TYPE_STRING, &ldi.music_set, "music_set" },
2333 { TYPE_STRING, &ldi.level_filename, "filename" },
2334 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2335 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2336 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2337 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" }
2340 static struct TokenInfo artworkinfo_tokens[] =
2342 /* artwork directory info */
2343 { TYPE_STRING, &ldi.identifier, "identifier" },
2344 { TYPE_STRING, &ldi.subdir, "subdir" },
2345 { TYPE_STRING, &ldi.name, "name" },
2346 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2347 { TYPE_STRING, &ldi.author, "author" },
2348 { TYPE_STRING, &ldi.program_title, "program_title" },
2349 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2350 { TYPE_STRING, &ldi.program_company, "program_company" },
2351 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2352 { TYPE_STRING, &ldi.basepath, "basepath" },
2353 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2354 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2355 { TYPE_INTEGER, &ldi.color, "color" },
2356 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2361 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2365 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2366 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2367 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2368 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2371 ti->node_parent = NULL;
2372 ti->node_group = NULL;
2379 ti->fullpath = NULL;
2380 ti->basepath = NULL;
2381 ti->identifier = NULL;
2382 ti->name = getStringCopy(ANONYMOUS_NAME);
2383 ti->name_sorting = NULL;
2384 ti->author = getStringCopy(ANONYMOUS_NAME);
2387 ti->program_title = NULL;
2388 ti->program_copyright = NULL;
2389 ti->program_company = NULL;
2391 ti->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
2392 ti->latest_engine = FALSE; /* default: get from level */
2393 ti->parent_link = FALSE;
2394 ti->in_user_dir = FALSE;
2395 ti->user_defined = FALSE;
2397 ti->class_desc = NULL;
2399 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2401 if (ti->type == TREE_TYPE_LEVEL_DIR)
2403 ti->imported_from = NULL;
2404 ti->imported_by = NULL;
2405 ti->tested_by = NULL;
2407 ti->graphics_set_ecs = NULL;
2408 ti->graphics_set_aga = NULL;
2409 ti->graphics_set = NULL;
2410 ti->sounds_set = NULL;
2411 ti->music_set = NULL;
2412 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2413 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2414 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2416 ti->level_filename = NULL;
2417 ti->level_filetype = NULL;
2419 ti->special_flags = NULL;
2422 ti->first_level = 0;
2424 ti->level_group = FALSE;
2425 ti->handicap_level = 0;
2426 ti->readonly = TRUE;
2427 ti->handicap = TRUE;
2428 ti->skip_levels = FALSE;
2432 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2436 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
2438 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2443 /* copy all values from the parent structure */
2445 ti->type = parent->type;
2447 ti->node_top = parent->node_top;
2448 ti->node_parent = parent;
2449 ti->node_group = NULL;
2456 ti->fullpath = NULL;
2457 ti->basepath = NULL;
2458 ti->identifier = NULL;
2459 ti->name = getStringCopy(ANONYMOUS_NAME);
2460 ti->name_sorting = NULL;
2461 ti->author = getStringCopy(parent->author);
2462 ti->year = getStringCopy(parent->year);
2464 ti->program_title = getStringCopy(parent->program_title);
2465 ti->program_copyright = getStringCopy(parent->program_copyright);
2466 ti->program_company = getStringCopy(parent->program_company);
2468 ti->sort_priority = parent->sort_priority;
2469 ti->latest_engine = parent->latest_engine;
2470 ti->parent_link = FALSE;
2471 ti->in_user_dir = parent->in_user_dir;
2472 ti->user_defined = parent->user_defined;
2473 ti->color = parent->color;
2474 ti->class_desc = getStringCopy(parent->class_desc);
2476 ti->infotext = getStringCopy(parent->infotext);
2478 if (ti->type == TREE_TYPE_LEVEL_DIR)
2480 ti->imported_from = getStringCopy(parent->imported_from);
2481 ti->imported_by = getStringCopy(parent->imported_by);
2482 ti->tested_by = getStringCopy(parent->tested_by);
2484 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2485 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2486 ti->graphics_set = getStringCopy(parent->graphics_set);
2487 ti->sounds_set = getStringCopy(parent->sounds_set);
2488 ti->music_set = getStringCopy(parent->music_set);
2489 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2490 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2491 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2493 ti->level_filename = getStringCopy(parent->level_filename);
2494 ti->level_filetype = getStringCopy(parent->level_filetype);
2496 ti->special_flags = getStringCopy(parent->special_flags);
2498 ti->levels = parent->levels;
2499 ti->first_level = parent->first_level;
2500 ti->last_level = parent->last_level;
2501 ti->level_group = FALSE;
2502 ti->handicap_level = parent->handicap_level;
2503 ti->readonly = parent->readonly;
2504 ti->handicap = parent->handicap;
2505 ti->skip_levels = parent->skip_levels;
2509 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2511 TreeInfo *ti_copy = newTreeInfo();
2513 /* copy all values from the original structure */
2515 ti_copy->type = ti->type;
2517 ti_copy->node_top = ti->node_top;
2518 ti_copy->node_parent = ti->node_parent;
2519 ti_copy->node_group = ti->node_group;
2520 ti_copy->next = ti->next;
2522 ti_copy->cl_first = ti->cl_first;
2523 ti_copy->cl_cursor = ti->cl_cursor;
2525 ti_copy->subdir = getStringCopy(ti->subdir);
2526 ti_copy->fullpath = getStringCopy(ti->fullpath);
2527 ti_copy->basepath = getStringCopy(ti->basepath);
2528 ti_copy->identifier = getStringCopy(ti->identifier);
2529 ti_copy->name = getStringCopy(ti->name);
2530 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2531 ti_copy->author = getStringCopy(ti->author);
2532 ti_copy->year = getStringCopy(ti->year);
2534 ti_copy->program_title = getStringCopy(ti->program_title);
2535 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2536 ti_copy->program_company = getStringCopy(ti->program_company);
2538 ti_copy->imported_from = getStringCopy(ti->imported_from);
2539 ti_copy->imported_by = getStringCopy(ti->imported_by);
2540 ti_copy->tested_by = getStringCopy(ti->tested_by);
2542 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2543 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2544 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2545 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2546 ti_copy->music_set = getStringCopy(ti->music_set);
2547 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2548 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2549 ti_copy->music_path = getStringCopy(ti->music_path);
2551 ti_copy->level_filename = getStringCopy(ti->level_filename);
2552 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2554 ti_copy->special_flags = getStringCopy(ti->special_flags);
2556 ti_copy->levels = ti->levels;
2557 ti_copy->first_level = ti->first_level;
2558 ti_copy->last_level = ti->last_level;
2559 ti_copy->sort_priority = ti->sort_priority;
2561 ti_copy->latest_engine = ti->latest_engine;
2563 ti_copy->level_group = ti->level_group;
2564 ti_copy->parent_link = ti->parent_link;
2565 ti_copy->in_user_dir = ti->in_user_dir;
2566 ti_copy->user_defined = ti->user_defined;
2567 ti_copy->readonly = ti->readonly;
2568 ti_copy->handicap = ti->handicap;
2569 ti_copy->skip_levels = ti->skip_levels;
2571 ti_copy->color = ti->color;
2572 ti_copy->class_desc = getStringCopy(ti->class_desc);
2573 ti_copy->handicap_level = ti->handicap_level;
2575 ti_copy->infotext = getStringCopy(ti->infotext);
2580 void freeTreeInfo(TreeInfo *ti)
2585 checked_free(ti->subdir);
2586 checked_free(ti->fullpath);
2587 checked_free(ti->basepath);
2588 checked_free(ti->identifier);
2590 checked_free(ti->name);
2591 checked_free(ti->name_sorting);
2592 checked_free(ti->author);
2593 checked_free(ti->year);
2595 checked_free(ti->program_title);
2596 checked_free(ti->program_copyright);
2597 checked_free(ti->program_company);
2599 checked_free(ti->class_desc);
2601 checked_free(ti->infotext);
2603 if (ti->type == TREE_TYPE_LEVEL_DIR)
2605 checked_free(ti->imported_from);
2606 checked_free(ti->imported_by);
2607 checked_free(ti->tested_by);
2609 checked_free(ti->graphics_set_ecs);
2610 checked_free(ti->graphics_set_aga);
2611 checked_free(ti->graphics_set);
2612 checked_free(ti->sounds_set);
2613 checked_free(ti->music_set);
2615 checked_free(ti->graphics_path);
2616 checked_free(ti->sounds_path);
2617 checked_free(ti->music_path);
2619 checked_free(ti->level_filename);
2620 checked_free(ti->level_filetype);
2622 checked_free(ti->special_flags);
2625 // recursively free child node
2627 freeTreeInfo(ti->node_group);
2629 // recursively free next node
2631 freeTreeInfo(ti->next);
2636 void setSetupInfo(struct TokenInfo *token_info,
2637 int token_nr, char *token_value)
2639 int token_type = token_info[token_nr].type;
2640 void *setup_value = token_info[token_nr].value;
2642 if (token_value == NULL)
2645 /* set setup field to corresponding token value */
2650 *(boolean *)setup_value = get_boolean_from_string(token_value);
2654 *(int *)setup_value = get_switch3_from_string(token_value);
2658 *(Key *)setup_value = getKeyFromKeyName(token_value);
2662 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2666 *(int *)setup_value = get_integer_from_string(token_value);
2670 checked_free(*(char **)setup_value);
2671 *(char **)setup_value = getStringCopy(token_value);
2675 *(int *)setup_value = get_player_nr_from_string(token_value);
2683 static int compareTreeInfoEntries(const void *object1, const void *object2)
2685 const TreeInfo *entry1 = *((TreeInfo **)object1);
2686 const TreeInfo *entry2 = *((TreeInfo **)object2);
2687 int class_sorting1 = 0, class_sorting2 = 0;
2690 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2692 class_sorting1 = LEVELSORTING(entry1);
2693 class_sorting2 = LEVELSORTING(entry2);
2695 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2696 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2697 entry1->type == TREE_TYPE_MUSIC_DIR)
2699 class_sorting1 = ARTWORKSORTING(entry1);
2700 class_sorting2 = ARTWORKSORTING(entry2);
2703 if (entry1->parent_link || entry2->parent_link)
2704 compare_result = (entry1->parent_link ? -1 : +1);
2705 else if (entry1->sort_priority == entry2->sort_priority)
2707 char *name1 = getStringToLower(entry1->name_sorting);
2708 char *name2 = getStringToLower(entry2->name_sorting);
2710 compare_result = strcmp(name1, name2);
2715 else if (class_sorting1 == class_sorting2)
2716 compare_result = entry1->sort_priority - entry2->sort_priority;
2718 compare_result = class_sorting1 - class_sorting2;
2720 return compare_result;
2723 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2727 if (node_parent == NULL)
2730 ti_new = newTreeInfo();
2731 setTreeInfoToDefaults(ti_new, node_parent->type);
2733 ti_new->node_parent = node_parent;
2734 ti_new->parent_link = TRUE;
2736 setString(&ti_new->identifier, node_parent->identifier);
2737 setString(&ti_new->name, ".. (parent directory)");
2738 setString(&ti_new->name_sorting, ti_new->name);
2740 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2741 setString(&ti_new->fullpath, node_parent->fullpath);
2743 ti_new->sort_priority = node_parent->sort_priority;
2744 ti_new->latest_engine = node_parent->latest_engine;
2746 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2748 pushTreeInfo(&node_parent->node_group, ti_new);
2753 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2755 TreeInfo *ti_new, *ti_new2;
2757 if (node_first == NULL)
2760 ti_new = newTreeInfo();
2761 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2763 ti_new->node_parent = NULL;
2764 ti_new->parent_link = FALSE;
2766 setString(&ti_new->identifier, node_first->identifier);
2767 setString(&ti_new->name, "level sets");
2768 setString(&ti_new->name_sorting, ti_new->name);
2770 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2771 setString(&ti_new->fullpath, ".");
2773 ti_new->sort_priority = node_first->sort_priority;;
2774 ti_new->latest_engine = node_first->latest_engine;
2776 setString(&ti_new->class_desc, "level sets");
2778 ti_new->node_group = node_first;
2779 ti_new->level_group = TRUE;
2781 ti_new2 = createParentTreeInfoNode(ti_new);
2783 setString(&ti_new2->name, ".. (main menu)");
2784 setString(&ti_new2->name_sorting, ti_new2->name);
2790 /* -------------------------------------------------------------------------- */
2791 /* functions for handling level and custom artwork info cache */
2792 /* -------------------------------------------------------------------------- */
2794 static void LoadArtworkInfoCache()
2796 InitCacheDirectory();
2798 if (artworkinfo_cache_old == NULL)
2800 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2802 /* try to load artwork info hash from already existing cache file */
2803 artworkinfo_cache_old = loadSetupFileHash(filename);
2805 /* if no artwork info cache file was found, start with empty hash */
2806 if (artworkinfo_cache_old == NULL)
2807 artworkinfo_cache_old = newSetupFileHash();
2812 if (artworkinfo_cache_new == NULL)
2813 artworkinfo_cache_new = newSetupFileHash();
2816 static void SaveArtworkInfoCache()
2818 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2820 InitCacheDirectory();
2822 saveSetupFileHash(artworkinfo_cache_new, filename);
2827 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2829 static char *prefix = NULL;
2831 checked_free(prefix);
2833 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2838 /* (identical to above function, but separate string buffer needed -- nasty) */
2839 static char *getCacheToken(char *prefix, char *suffix)
2841 static char *token = NULL;
2843 checked_free(token);
2845 token = getStringCat2WithSeparator(prefix, suffix, ".");
2850 static char *getFileTimestampString(char *filename)
2852 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2855 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2857 struct stat file_status;
2859 if (timestamp_string == NULL)
2862 if (stat(filename, &file_status) != 0) /* cannot stat file */
2865 return (file_status.st_mtime != atoi(timestamp_string));
2868 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2870 char *identifier = level_node->subdir;
2871 char *type_string = ARTWORK_DIRECTORY(type);
2872 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2873 char *token_main = getCacheToken(token_prefix, "CACHED");
2874 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2875 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2876 TreeInfo *artwork_info = NULL;
2878 if (!use_artworkinfo_cache)
2885 artwork_info = newTreeInfo();
2886 setTreeInfoToDefaults(artwork_info, type);
2888 /* set all structure fields according to the token/value pairs */
2889 ldi = *artwork_info;
2890 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2892 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2893 char *value = getHashEntry(artworkinfo_cache_old, token);
2895 /* if defined, use value from cache, else keep default value */
2897 setSetupInfo(artworkinfo_tokens, i, value);
2900 *artwork_info = ldi;
2902 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2903 LEVELINFO_FILENAME);
2904 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2905 ARTWORKINFO_FILENAME(type));
2907 /* check if corresponding "levelinfo.conf" file has changed */
2908 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2909 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2911 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
2914 /* check if corresponding "<artworkinfo>.conf" file has changed */
2915 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2916 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2918 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
2921 checked_free(filename_levelinfo);
2922 checked_free(filename_artworkinfo);
2925 if (!cached && artwork_info != NULL)
2927 freeTreeInfo(artwork_info);
2932 return artwork_info;
2935 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
2936 LevelDirTree *level_node, int type)
2938 char *identifier = level_node->subdir;
2939 char *type_string = ARTWORK_DIRECTORY(type);
2940 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2941 char *token_main = getCacheToken(token_prefix, "CACHED");
2942 boolean set_cache_timestamps = TRUE;
2945 setHashEntry(artworkinfo_cache_new, token_main, "true");
2947 if (set_cache_timestamps)
2949 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
2950 LEVELINFO_FILENAME);
2951 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
2952 ARTWORKINFO_FILENAME(type));
2953 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
2954 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
2956 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
2957 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
2959 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
2960 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
2962 checked_free(filename_levelinfo);
2963 checked_free(filename_artworkinfo);
2964 checked_free(timestamp_levelinfo);
2965 checked_free(timestamp_artworkinfo);
2968 ldi = *artwork_info;
2969 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2971 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
2972 char *value = getSetupValue(artworkinfo_tokens[i].type,
2973 artworkinfo_tokens[i].value);
2975 setHashEntry(artworkinfo_cache_new, token, value);
2980 /* -------------------------------------------------------------------------- */
2981 /* functions for loading level info and custom artwork info */
2982 /* -------------------------------------------------------------------------- */
2984 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
2985 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
2987 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
2988 TreeInfo *node_parent,
2989 char *level_directory,
2990 char *directory_name)
2992 char *directory_path = getPath2(level_directory, directory_name);
2993 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
2994 SetupFileHash *setup_file_hash;
2995 LevelDirTree *leveldir_new = NULL;
2998 /* unless debugging, silently ignore directories without "levelinfo.conf" */
2999 if (!options.debug && !fileExists(filename))
3001 free(directory_path);
3007 setup_file_hash = loadSetupFileHash(filename);
3009 if (setup_file_hash == NULL)
3011 #if DEBUG_NO_CONFIG_FILE
3012 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
3015 free(directory_path);
3021 leveldir_new = newTreeInfo();
3024 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3026 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3028 leveldir_new->subdir = getStringCopy(directory_name);
3030 /* set all structure fields according to the token/value pairs */
3031 ldi = *leveldir_new;
3032 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3033 setSetupInfo(levelinfo_tokens, i,
3034 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3035 *leveldir_new = ldi;
3037 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3038 setString(&leveldir_new->name, leveldir_new->subdir);
3040 if (leveldir_new->identifier == NULL)
3041 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3043 if (leveldir_new->name_sorting == NULL)
3044 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3046 if (node_parent == NULL) /* top level group */
3048 leveldir_new->basepath = getStringCopy(level_directory);
3049 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3051 else /* sub level group */
3053 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3054 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3057 leveldir_new->last_level =
3058 leveldir_new->first_level + leveldir_new->levels - 1;
3060 leveldir_new->in_user_dir =
3061 (!strEqual(leveldir_new->basepath, options.level_directory));
3063 /* adjust some settings if user's private level directory was detected */
3064 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3065 leveldir_new->in_user_dir &&
3066 (strEqual(leveldir_new->subdir, getLoginName()) ||
3067 strEqual(leveldir_new->name, getLoginName()) ||
3068 strEqual(leveldir_new->author, getRealName())))
3070 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3071 leveldir_new->readonly = FALSE;
3074 leveldir_new->user_defined =
3075 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3077 leveldir_new->color = LEVELCOLOR(leveldir_new);
3079 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3081 leveldir_new->handicap_level = /* set handicap to default value */
3082 (leveldir_new->user_defined || !leveldir_new->handicap ?
3083 leveldir_new->last_level : leveldir_new->first_level);
3085 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3087 pushTreeInfo(node_first, leveldir_new);
3089 freeSetupFileHash(setup_file_hash);
3091 if (leveldir_new->level_group)
3093 /* create node to link back to current level directory */
3094 createParentTreeInfoNode(leveldir_new);
3096 /* recursively step into sub-directory and look for more level series */
3097 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3098 leveldir_new, directory_path);
3101 free(directory_path);
3107 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3108 TreeInfo *node_parent,
3109 char *level_directory)
3112 DirectoryEntry *dir_entry;
3113 boolean valid_entry_found = FALSE;
3115 if ((dir = openDirectory(level_directory)) == NULL)
3117 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
3122 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3124 char *directory_name = dir_entry->basename;
3125 char *directory_path = getPath2(level_directory, directory_name);
3127 /* skip entries for current and parent directory */
3128 if (strEqual(directory_name, ".") ||
3129 strEqual(directory_name, ".."))
3131 free(directory_path);
3136 /* find out if directory entry is itself a directory */
3137 if (!dir_entry->is_directory) /* not a directory */
3139 free(directory_path);
3144 free(directory_path);
3146 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3147 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3148 strEqual(directory_name, MUSIC_DIRECTORY))
3151 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3156 closeDirectory(dir);
3158 /* special case: top level directory may directly contain "levelinfo.conf" */
3159 if (node_parent == NULL && !valid_entry_found)
3161 /* check if this directory directly contains a file "levelinfo.conf" */
3162 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3163 level_directory, ".");
3166 if (!valid_entry_found)
3167 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
3171 boolean AdjustGraphicsForEMC()
3173 boolean settings_changed = FALSE;
3175 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3176 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3178 return settings_changed;
3181 void LoadLevelInfo()
3183 InitUserLevelDirectory(getLoginName());
3185 DrawInitText("Loading level series", 120, FC_GREEN);
3187 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3188 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3190 leveldir_first = createTopTreeInfoNode(leveldir_first);
3192 /* after loading all level set information, clone the level directory tree
3193 and remove all level sets without levels (these may still contain artwork
3194 to be offered in the setup menu as "custom artwork", and are therefore
3195 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3196 leveldir_first_all = leveldir_first;
3197 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3199 AdjustGraphicsForEMC();
3201 /* before sorting, the first entries will be from the user directory */
3202 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3204 if (leveldir_first == NULL)
3205 Error(ERR_EXIT, "cannot find any valid level series in any directory");
3207 sortTreeInfo(&leveldir_first);
3209 #if ENABLE_UNUSED_CODE
3210 dumpTreeInfo(leveldir_first, 0);
3214 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3215 TreeInfo *node_parent,
3216 char *base_directory,
3217 char *directory_name, int type)
3219 char *directory_path = getPath2(base_directory, directory_name);
3220 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3221 SetupFileHash *setup_file_hash = NULL;
3222 TreeInfo *artwork_new = NULL;
3225 if (fileExists(filename))
3226 setup_file_hash = loadSetupFileHash(filename);
3228 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
3231 DirectoryEntry *dir_entry;
3232 boolean valid_file_found = FALSE;
3234 if ((dir = openDirectory(directory_path)) != NULL)
3236 while ((dir_entry = readDirectory(dir)) != NULL)
3238 if (FileIsArtworkType(dir_entry->filename, type))
3240 valid_file_found = TRUE;
3246 closeDirectory(dir);
3249 if (!valid_file_found)
3251 #if DEBUG_NO_CONFIG_FILE
3252 if (!strEqual(directory_name, "."))
3253 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
3256 free(directory_path);
3263 artwork_new = newTreeInfo();
3266 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3268 setTreeInfoToDefaults(artwork_new, type);
3270 artwork_new->subdir = getStringCopy(directory_name);
3272 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
3274 /* set all structure fields according to the token/value pairs */
3276 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3277 setSetupInfo(levelinfo_tokens, i,
3278 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3281 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3282 setString(&artwork_new->name, artwork_new->subdir);
3284 if (artwork_new->identifier == NULL)
3285 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3287 if (artwork_new->name_sorting == NULL)
3288 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3291 if (node_parent == NULL) /* top level group */
3293 artwork_new->basepath = getStringCopy(base_directory);
3294 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3296 else /* sub level group */
3298 artwork_new->basepath = getStringCopy(node_parent->basepath);
3299 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3302 artwork_new->in_user_dir =
3303 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3305 /* (may use ".sort_priority" from "setup_file_hash" above) */
3306 artwork_new->color = ARTWORKCOLOR(artwork_new);
3308 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3310 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
3312 if (strEqual(artwork_new->subdir, "."))
3314 if (artwork_new->user_defined)
3316 setString(&artwork_new->identifier, "private");
3317 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3321 setString(&artwork_new->identifier, "classic");
3322 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3325 /* set to new values after changing ".sort_priority" */
3326 artwork_new->color = ARTWORKCOLOR(artwork_new);
3328 setString(&artwork_new->class_desc,
3329 getLevelClassDescription(artwork_new));
3333 setString(&artwork_new->identifier, artwork_new->subdir);
3336 setString(&artwork_new->name, artwork_new->identifier);
3337 setString(&artwork_new->name_sorting, artwork_new->name);
3340 pushTreeInfo(node_first, artwork_new);
3342 freeSetupFileHash(setup_file_hash);
3344 free(directory_path);
3350 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3351 TreeInfo *node_parent,
3352 char *base_directory, int type)
3355 DirectoryEntry *dir_entry;
3356 boolean valid_entry_found = FALSE;
3358 if ((dir = openDirectory(base_directory)) == NULL)
3360 /* display error if directory is main "options.graphics_directory" etc. */
3361 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3362 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
3367 while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */
3369 char *directory_name = dir_entry->basename;
3370 char *directory_path = getPath2(base_directory, directory_name);
3372 /* skip directory entries for current and parent directory */
3373 if (strEqual(directory_name, ".") ||
3374 strEqual(directory_name, ".."))
3376 free(directory_path);
3381 /* skip directory entries which are not a directory */
3382 if (!dir_entry->is_directory) /* not a directory */
3384 free(directory_path);
3389 free(directory_path);
3391 /* check if this directory contains artwork with or without config file */
3392 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3394 directory_name, type);
3397 closeDirectory(dir);
3399 /* check if this directory directly contains artwork itself */
3400 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3401 base_directory, ".",
3403 if (!valid_entry_found)
3404 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
3408 static TreeInfo *getDummyArtworkInfo(int type)
3410 /* this is only needed when there is completely no artwork available */
3411 TreeInfo *artwork_new = newTreeInfo();
3413 setTreeInfoToDefaults(artwork_new, type);
3415 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3416 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3417 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3419 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3420 setString(&artwork_new->name, UNDEFINED_FILENAME);
3421 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3426 void LoadArtworkInfo()
3428 LoadArtworkInfoCache();
3430 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3432 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3433 options.graphics_directory,
3434 TREE_TYPE_GRAPHICS_DIR);
3435 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3436 getUserGraphicsDir(),
3437 TREE_TYPE_GRAPHICS_DIR);
3439 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3440 options.sounds_directory,
3441 TREE_TYPE_SOUNDS_DIR);
3442 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3444 TREE_TYPE_SOUNDS_DIR);
3446 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3447 options.music_directory,
3448 TREE_TYPE_MUSIC_DIR);
3449 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3451 TREE_TYPE_MUSIC_DIR);
3453 if (artwork.gfx_first == NULL)
3454 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3455 if (artwork.snd_first == NULL)
3456 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3457 if (artwork.mus_first == NULL)
3458 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3460 /* before sorting, the first entries will be from the user directory */
3461 artwork.gfx_current =
3462 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3463 if (artwork.gfx_current == NULL)
3464 artwork.gfx_current =
3465 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3466 if (artwork.gfx_current == NULL)
3467 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3469 artwork.snd_current =
3470 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3471 if (artwork.snd_current == NULL)
3472 artwork.snd_current =
3473 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3474 if (artwork.snd_current == NULL)
3475 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3477 artwork.mus_current =
3478 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3479 if (artwork.mus_current == NULL)
3480 artwork.mus_current =
3481 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3482 if (artwork.mus_current == NULL)
3483 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3485 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3486 artwork.snd_current_identifier = artwork.snd_current->identifier;
3487 artwork.mus_current_identifier = artwork.mus_current->identifier;
3489 #if ENABLE_UNUSED_CODE
3490 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
3491 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
3492 printf("music set == %s\n\n", artwork.mus_current_identifier);
3495 sortTreeInfo(&artwork.gfx_first);
3496 sortTreeInfo(&artwork.snd_first);
3497 sortTreeInfo(&artwork.mus_first);
3499 #if ENABLE_UNUSED_CODE
3500 dumpTreeInfo(artwork.gfx_first, 0);
3501 dumpTreeInfo(artwork.snd_first, 0);
3502 dumpTreeInfo(artwork.mus_first, 0);
3506 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3507 LevelDirTree *level_node)
3509 int type = (*artwork_node)->type;
3511 /* recursively check all level directories for artwork sub-directories */
3515 /* check all tree entries for artwork, but skip parent link entries */
3516 if (!level_node->parent_link)
3518 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3519 boolean cached = (artwork_new != NULL);
3523 pushTreeInfo(artwork_node, artwork_new);
3527 TreeInfo *topnode_last = *artwork_node;
3528 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3529 ARTWORK_DIRECTORY(type));
3531 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3533 if (topnode_last != *artwork_node) /* check for newly added node */
3535 artwork_new = *artwork_node;
3537 setString(&artwork_new->identifier, level_node->subdir);
3538 setString(&artwork_new->name, level_node->name);
3539 setString(&artwork_new->name_sorting, level_node->name_sorting);
3541 artwork_new->sort_priority = level_node->sort_priority;
3542 artwork_new->color = LEVELCOLOR(artwork_new);
3548 /* insert artwork info (from old cache or filesystem) into new cache */
3549 if (artwork_new != NULL)
3550 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3553 DrawInitText(level_node->name, 150, FC_YELLOW);
3555 if (level_node->node_group != NULL)
3556 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3558 level_node = level_node->next;
3562 void LoadLevelArtworkInfo()
3564 print_timestamp_init("LoadLevelArtworkInfo");
3566 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3568 print_timestamp_time("DrawTimeText");
3570 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3571 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3572 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3573 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3574 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3575 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3577 SaveArtworkInfoCache();
3579 print_timestamp_time("SaveArtworkInfoCache");
3581 /* needed for reloading level artwork not known at ealier stage */
3583 if (!strEqual(artwork.gfx_current_identifier, setup.graphics_set))
3585 artwork.gfx_current =
3586 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
3587 if (artwork.gfx_current == NULL)
3588 artwork.gfx_current =
3589 getTreeInfoFromIdentifier(artwork.gfx_first, GFX_DEFAULT_SUBDIR);
3590 if (artwork.gfx_current == NULL)
3591 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
3594 if (!strEqual(artwork.snd_current_identifier, setup.sounds_set))
3596 artwork.snd_current =
3597 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
3598 if (artwork.snd_current == NULL)
3599 artwork.snd_current =
3600 getTreeInfoFromIdentifier(artwork.snd_first, SND_DEFAULT_SUBDIR);
3601 if (artwork.snd_current == NULL)
3602 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
3605 if (!strEqual(artwork.mus_current_identifier, setup.music_set))
3607 artwork.mus_current =
3608 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
3609 if (artwork.mus_current == NULL)
3610 artwork.mus_current =
3611 getTreeInfoFromIdentifier(artwork.mus_first, MUS_DEFAULT_SUBDIR);
3612 if (artwork.mus_current == NULL)
3613 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
3616 print_timestamp_time("getTreeInfoFromIdentifier");
3618 sortTreeInfo(&artwork.gfx_first);
3619 sortTreeInfo(&artwork.snd_first);
3620 sortTreeInfo(&artwork.mus_first);
3622 print_timestamp_time("sortTreeInfo");
3624 #if ENABLE_UNUSED_CODE
3625 dumpTreeInfo(artwork.gfx_first, 0);
3626 dumpTreeInfo(artwork.snd_first, 0);
3627 dumpTreeInfo(artwork.mus_first, 0);
3630 print_timestamp_done("LoadLevelArtworkInfo");
3633 static boolean AddUserLevelSetToLevelInfoExt(char *level_subdir_new)
3635 // get level info tree node of first (original) user level set
3636 char *level_subdir_old = getLoginName();
3637 LevelDirTree *leveldir_old = getTreeInfoFromIdentifier(leveldir_first,
3639 if (leveldir_old == NULL) // should not happen
3642 int draw_deactivation_mask = GetDrawDeactivationMask();
3644 // override draw deactivation mask (temporarily disable drawing)
3645 SetDrawDeactivationMask(REDRAW_ALL);
3647 // load new level set config and add it next to first user level set
3648 LoadLevelInfoFromLevelConf(&leveldir_old->next, NULL,
3649 leveldir_old->basepath, level_subdir_new);
3651 // set draw deactivation mask to previous value
3652 SetDrawDeactivationMask(draw_deactivation_mask);
3654 // get level info tree node of newly added user level set
3655 LevelDirTree *leveldir_new = getTreeInfoFromIdentifier(leveldir_first,
3657 if (leveldir_new == NULL) // should not happen
3660 // correct top link and parent node link of newly created tree node
3661 leveldir_new->node_top = leveldir_old->node_top;
3662 leveldir_new->node_parent = leveldir_old->node_parent;
3664 // sort level info tree to adjust position of newly added level set
3665 sortTreeInfo(&leveldir_first);
3670 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
3672 if (!AddUserLevelSetToLevelInfoExt(level_subdir_new))
3673 Error(ERR_EXIT, "internal level set structure corrupted -- aborting");
3676 char *getArtworkIdentifierForUserLevelSet(int type)
3678 char *classic_artwork_set = getClassicArtworkSet(type);
3680 /* check for custom artwork configured in "levelinfo.conf" */
3681 char *leveldir_artwork_set =
3682 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
3683 boolean has_leveldir_artwork_set =
3684 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
3685 classic_artwork_set));
3687 /* check for custom artwork in sub-directory "graphics" etc. */
3688 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3689 char *leveldir_identifier = leveldir_current->identifier;
3690 boolean has_artwork_subdir =
3691 (getTreeInfoFromIdentifier(artwork_first_node,
3692 leveldir_identifier) != NULL);
3694 return (has_leveldir_artwork_set ? leveldir_artwork_set :
3695 has_artwork_subdir ? leveldir_identifier :
3696 classic_artwork_set);
3699 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
3701 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
3702 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
3704 return getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
3707 boolean checkIfCustomArtworkExistsForCurrentLevelSet()
3709 char *graphics_set =
3710 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
3712 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
3714 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
3716 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
3717 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
3718 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
3721 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
3722 char *level_author, int num_levels)
3724 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3725 char *filename_tmp = getStringCat2(filename, ".tmp");
3727 FILE *file_tmp = NULL;
3728 char line[MAX_LINE_LEN];
3729 boolean success = FALSE;
3730 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
3732 // update values in level directory tree
3734 if (level_name != NULL)
3735 setString(&leveldir->name, level_name);
3737 if (level_author != NULL)
3738 setString(&leveldir->author, level_author);
3740 if (num_levels != -1)
3741 leveldir->levels = num_levels;
3743 // update values that depend on other values
3745 setString(&leveldir->name_sorting, leveldir->name);
3747 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
3749 // sort order of level sets may have changed
3750 sortTreeInfo(&leveldir_first);
3752 if ((file = fopen(filename, MODE_READ)) &&
3753 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
3755 while (fgets(line, MAX_LINE_LEN, file))
3757 if (strPrefix(line, "name:") && level_name != NULL)
3758 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
3759 else if (strPrefix(line, "author:") && level_author != NULL)
3760 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
3761 else if (strPrefix(line, "levels:") && num_levels != -1)
3762 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
3764 fputs(line, file_tmp);
3777 success = (rename(filename_tmp, filename) == 0);
3785 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
3786 char *level_author, int num_levels,
3787 boolean use_artwork_set)
3789 LevelDirTree *level_info;
3794 // create user level sub-directory, if needed
3795 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
3797 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
3799 if (!(file = fopen(filename, MODE_WRITE)))
3801 Error(ERR_WARN, "cannot write level info file '%s'", filename);
3807 level_info = newTreeInfo();
3809 /* always start with reliable default values */
3810 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
3812 setString(&level_info->name, level_name);
3813 setString(&level_info->author, level_author);
3814 level_info->levels = num_levels;
3815 level_info->first_level = 1;
3816 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
3817 level_info->readonly = FALSE;
3819 if (use_artwork_set)
3821 level_info->graphics_set =
3822 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
3823 level_info->sounds_set =
3824 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
3825 level_info->music_set =
3826 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
3829 token_value_position = TOKEN_VALUE_POSITION_SHORT;
3831 fprintFileHeader(file, LEVELINFO_FILENAME);
3834 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3836 if (i == LEVELINFO_TOKEN_NAME ||
3837 i == LEVELINFO_TOKEN_AUTHOR ||
3838 i == LEVELINFO_TOKEN_LEVELS ||
3839 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3840 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
3841 i == LEVELINFO_TOKEN_READONLY ||
3842 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
3843 i == LEVELINFO_TOKEN_SOUNDS_SET ||
3844 i == LEVELINFO_TOKEN_MUSIC_SET)))
3845 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
3847 /* just to make things nicer :) */
3848 if (i == LEVELINFO_TOKEN_AUTHOR ||
3849 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
3850 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
3851 fprintf(file, "\n");
3854 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
3858 SetFilePermissions(filename, PERMS_PRIVATE);
3860 freeTreeInfo(level_info);
3866 static void SaveUserLevelInfo()
3868 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
3871 char *getSetupValue(int type, void *value)
3873 static char value_string[MAX_LINE_LEN];
3881 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
3885 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
3889 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3890 *(int *)value == FALSE ? "off" : "on"));
3894 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
3897 case TYPE_YES_NO_AUTO:
3898 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
3899 *(int *)value == FALSE ? "no" : "yes"));
3903 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
3907 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
3911 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
3915 sprintf(value_string, "%d", *(int *)value);
3919 if (*(char **)value == NULL)
3922 strcpy(value_string, *(char **)value);
3926 sprintf(value_string, "player_%d", *(int *)value + 1);
3930 value_string[0] = '\0';
3934 if (type & TYPE_GHOSTED)
3935 strcpy(value_string, "n/a");
3937 return value_string;
3940 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
3944 static char token_string[MAX_LINE_LEN];
3945 int token_type = token_info[token_nr].type;
3946 void *setup_value = token_info[token_nr].value;
3947 char *token_text = token_info[token_nr].text;
3948 char *value_string = getSetupValue(token_type, setup_value);
3950 /* build complete token string */
3951 sprintf(token_string, "%s%s", prefix, token_text);
3953 /* build setup entry line */
3954 line = getFormattedSetupEntry(token_string, value_string);
3956 if (token_type == TYPE_KEY_X11)
3958 Key key = *(Key *)setup_value;
3959 char *keyname = getKeyNameFromKey(key);
3961 /* add comment, if useful */
3962 if (!strEqual(keyname, "(undefined)") &&
3963 !strEqual(keyname, "(unknown)"))
3965 /* add at least one whitespace */
3967 for (i = strlen(line); i < token_comment_position; i++)
3971 strcat(line, keyname);
3978 void LoadLevelSetup_LastSeries()
3980 /* ----------------------------------------------------------------------- */
3981 /* ~/.<program>/levelsetup.conf */
3982 /* ----------------------------------------------------------------------- */
3984 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
3985 SetupFileHash *level_setup_hash = NULL;
3987 /* always start with reliable default values */
3988 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3990 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
3992 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
3994 if (leveldir_current == NULL)
3995 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3998 if ((level_setup_hash = loadSetupFileHash(filename)))
4000 char *last_level_series =
4001 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4003 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4005 if (leveldir_current == NULL)
4006 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4008 freeSetupFileHash(level_setup_hash);
4012 Error(ERR_DEBUG, "using default setup values");
4018 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4020 /* ----------------------------------------------------------------------- */
4021 /* ~/.<program>/levelsetup.conf */
4022 /* ----------------------------------------------------------------------- */
4024 // check if the current level directory structure is available at this point
4025 if (leveldir_current == NULL)
4028 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4029 char *level_subdir = leveldir_current->subdir;
4032 InitUserDataDirectory();
4034 if (!(file = fopen(filename, MODE_WRITE)))
4036 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4043 fprintFileHeader(file, LEVELSETUP_FILENAME);
4045 if (deactivate_last_level_series)
4046 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4048 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4053 SetFilePermissions(filename, PERMS_PRIVATE);
4058 void SaveLevelSetup_LastSeries()
4060 SaveLevelSetup_LastSeries_Ext(FALSE);
4063 void SaveLevelSetup_LastSeries_Deactivate()
4065 SaveLevelSetup_LastSeries_Ext(TRUE);
4068 static void checkSeriesInfo()
4070 static char *level_directory = NULL;
4073 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
4075 level_directory = getPath2((leveldir_current->in_user_dir ?
4076 getUserLevelDir(NULL) :
4077 options.level_directory),
4078 leveldir_current->fullpath);
4080 if ((dir = openDirectory(level_directory)) == NULL)
4082 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
4087 closeDirectory(dir);
4090 void LoadLevelSetup_SeriesInfo()
4093 SetupFileHash *level_setup_hash = NULL;
4094 char *level_subdir = leveldir_current->subdir;
4097 /* always start with reliable default values */
4098 level_nr = leveldir_current->first_level;
4100 for (i = 0; i < MAX_LEVELS; i++)
4102 LevelStats_setPlayed(i, 0);
4103 LevelStats_setSolved(i, 0);
4108 /* ----------------------------------------------------------------------- */
4109 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4110 /* ----------------------------------------------------------------------- */
4112 level_subdir = leveldir_current->subdir;
4114 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4116 if ((level_setup_hash = loadSetupFileHash(filename)))
4120 /* get last played level in this level set */
4122 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4126 level_nr = atoi(token_value);
4128 if (level_nr < leveldir_current->first_level)
4129 level_nr = leveldir_current->first_level;
4130 if (level_nr > leveldir_current->last_level)
4131 level_nr = leveldir_current->last_level;
4134 /* get handicap level in this level set */
4136 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4140 int level_nr = atoi(token_value);
4142 if (level_nr < leveldir_current->first_level)
4143 level_nr = leveldir_current->first_level;
4144 if (level_nr > leveldir_current->last_level + 1)
4145 level_nr = leveldir_current->last_level;
4147 if (leveldir_current->user_defined || !leveldir_current->handicap)
4148 level_nr = leveldir_current->last_level;
4150 leveldir_current->handicap_level = level_nr;
4153 /* get number of played and solved levels in this level set */
4155 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4157 char *token = HASH_ITERATION_TOKEN(itr);
4158 char *value = HASH_ITERATION_VALUE(itr);
4160 if (strlen(token) == 3 &&
4161 token[0] >= '0' && token[0] <= '9' &&
4162 token[1] >= '0' && token[1] <= '9' &&
4163 token[2] >= '0' && token[2] <= '9')
4165 int level_nr = atoi(token);
4168 LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */
4170 value = strchr(value, ' ');
4173 LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */
4176 END_HASH_ITERATION(hash, itr)
4178 freeSetupFileHash(level_setup_hash);
4182 Error(ERR_DEBUG, "using default setup values");
4188 void SaveLevelSetup_SeriesInfo()
4191 char *level_subdir = leveldir_current->subdir;
4192 char *level_nr_str = int2str(level_nr, 0);
4193 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4197 /* ----------------------------------------------------------------------- */
4198 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
4199 /* ----------------------------------------------------------------------- */
4201 InitLevelSetupDirectory(level_subdir);
4203 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4205 if (!(file = fopen(filename, MODE_WRITE)))
4207 Error(ERR_WARN, "cannot write setup file '%s'", filename);
4212 fprintFileHeader(file, LEVELSETUP_FILENAME);
4214 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4216 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4217 handicap_level_str));
4219 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4222 if (LevelStats_getPlayed(i) > 0 ||
4223 LevelStats_getSolved(i) > 0)
4228 sprintf(token, "%03d", i);
4229 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4231 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4237 SetFilePermissions(filename, PERMS_PRIVATE);
4242 int LevelStats_getPlayed(int nr)
4244 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4247 int LevelStats_getSolved(int nr)
4249 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4252 void LevelStats_setPlayed(int nr, int value)
4254 if (nr >= 0 && nr < MAX_LEVELS)
4255 level_stats[nr].played = value;
4258 void LevelStats_setSolved(int nr, int value)
4260 if (nr >= 0 && nr < MAX_LEVELS)
4261 level_stats[nr].solved = value;
4264 void LevelStats_incPlayed(int nr)
4266 if (nr >= 0 && nr < MAX_LEVELS)
4267 level_stats[nr].played++;
4270 void LevelStats_incSolved(int nr)
4272 if (nr >= 0 && nr < MAX_LEVELS)
4273 level_stats[nr].solved++;