1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
47 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
48 IS_LEVELCLASS_BD(n) ? FC_GREEN : \
49 IS_LEVELCLASS_EM(n) ? FC_GREEN : \
50 IS_LEVELCLASS_SP(n) ? FC_GREEN : \
51 IS_LEVELCLASS_DX(n) ? FC_GREEN : \
52 IS_LEVELCLASS_SB(n) ? FC_GREEN : \
53 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
54 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
57 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
58 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
59 IS_LEVELCLASS_BD(n) ? 2 : \
60 IS_LEVELCLASS_EM(n) ? 3 : \
61 IS_LEVELCLASS_SP(n) ? 4 : \
62 IS_LEVELCLASS_DX(n) ? 5 : \
63 IS_LEVELCLASS_SB(n) ? 6 : \
64 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
65 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
68 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
69 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
70 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
71 IS_ARTWORKCLASS_LEVEL(n) ? FC_GREEN : \
74 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
75 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
76 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
77 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
80 #define TOKEN_VALUE_POSITION_SHORT 32
81 #define TOKEN_VALUE_POSITION_DEFAULT 40
82 #define TOKEN_COMMENT_POSITION_DEFAULT 60
84 #define MAX_COOKIE_LEN 256
87 static void setTreeInfoToDefaults(TreeInfo *, int);
88 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
89 static int compareTreeInfoEntries(const void *, const void *);
91 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
92 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
94 static SetupFileHash *artworkinfo_cache_old = NULL;
95 static SetupFileHash *artworkinfo_cache_new = NULL;
96 static SetupFileHash *optional_tokens_hash = NULL;
97 static boolean use_artworkinfo_cache = TRUE;
98 static boolean update_artworkinfo_cache = FALSE;
101 // ----------------------------------------------------------------------------
103 // ----------------------------------------------------------------------------
105 static char *getLevelClassDescription(TreeInfo *ti)
107 int position = ti->sort_priority / 100;
109 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
110 return levelclass_desc[position];
112 return "Unknown Level Class";
115 static char *getScoreDir(char *level_subdir)
117 static char *score_dir = NULL;
118 static char *score_level_dir = NULL;
119 char *score_subdir = SCORES_DIRECTORY;
121 if (score_dir == NULL)
123 if (program.global_scores)
124 score_dir = getPath2(getCommonDataDir(), score_subdir);
126 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
129 if (level_subdir != NULL)
131 checked_free(score_level_dir);
133 score_level_dir = getPath2(score_dir, level_subdir);
135 return score_level_dir;
141 static char *getUserSubdir(int nr)
143 static char user_subdir[16] = { 0 };
145 sprintf(user_subdir, "%03d", nr);
150 static char *getUserDir(int nr)
152 static char *user_dir = NULL;
153 char *main_data_dir = getMainUserGameDataDir();
154 char *users_subdir = USERS_DIRECTORY;
155 char *user_subdir = getUserSubdir(nr);
157 checked_free(user_dir);
160 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
162 user_dir = getPath2(main_data_dir, users_subdir);
167 static char *getLevelSetupDir(char *level_subdir)
169 static char *levelsetup_dir = NULL;
170 char *data_dir = getUserGameDataDir();
171 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
173 checked_free(levelsetup_dir);
175 if (level_subdir != NULL)
176 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
178 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
180 return levelsetup_dir;
183 static char *getCacheDir(void)
185 static char *cache_dir = NULL;
187 if (cache_dir == NULL)
188 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
193 static char *getNetworkDir(void)
195 static char *network_dir = NULL;
197 if (network_dir == NULL)
198 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
203 char *getLevelDirFromTreeInfo(TreeInfo *node)
205 static char *level_dir = NULL;
208 return options.level_directory;
210 checked_free(level_dir);
212 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
213 options.level_directory), node->fullpath);
218 char *getUserLevelDir(char *level_subdir)
220 static char *userlevel_dir = NULL;
221 char *data_dir = getMainUserGameDataDir();
222 char *userlevel_subdir = LEVELS_DIRECTORY;
224 checked_free(userlevel_dir);
226 if (level_subdir != NULL)
227 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
229 userlevel_dir = getPath2(data_dir, userlevel_subdir);
231 return userlevel_dir;
234 char *getNetworkLevelDir(char *level_subdir)
236 static char *network_level_dir = NULL;
237 char *data_dir = getNetworkDir();
238 char *networklevel_subdir = LEVELS_DIRECTORY;
240 checked_free(network_level_dir);
242 if (level_subdir != NULL)
243 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
245 network_level_dir = getPath2(data_dir, networklevel_subdir);
247 return network_level_dir;
250 char *getCurrentLevelDir(void)
252 return getLevelDirFromTreeInfo(leveldir_current);
255 char *getNewUserLevelSubdir(void)
257 static char *new_level_subdir = NULL;
258 char *subdir_prefix = getLoginName();
259 char subdir_suffix[10];
260 int max_suffix_number = 1000;
263 while (++i < max_suffix_number)
265 sprintf(subdir_suffix, "_%d", i);
267 checked_free(new_level_subdir);
268 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
270 if (!directoryExists(getUserLevelDir(new_level_subdir)))
274 return new_level_subdir;
277 static char *getTapeDir(char *level_subdir)
279 static char *tape_dir = NULL;
280 char *data_dir = getUserGameDataDir();
281 char *tape_subdir = TAPES_DIRECTORY;
283 checked_free(tape_dir);
285 if (level_subdir != NULL)
286 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
288 tape_dir = getPath2(data_dir, tape_subdir);
293 static char *getSolutionTapeDir(void)
295 static char *tape_dir = NULL;
296 char *data_dir = getCurrentLevelDir();
297 char *tape_subdir = TAPES_DIRECTORY;
299 checked_free(tape_dir);
301 tape_dir = getPath2(data_dir, tape_subdir);
306 static char *getDefaultGraphicsDir(char *graphics_subdir)
308 static char *graphics_dir = NULL;
310 if (graphics_subdir == NULL)
311 return options.graphics_directory;
313 checked_free(graphics_dir);
315 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
320 static char *getDefaultSoundsDir(char *sounds_subdir)
322 static char *sounds_dir = NULL;
324 if (sounds_subdir == NULL)
325 return options.sounds_directory;
327 checked_free(sounds_dir);
329 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
334 static char *getDefaultMusicDir(char *music_subdir)
336 static char *music_dir = NULL;
338 if (music_subdir == NULL)
339 return options.music_directory;
341 checked_free(music_dir);
343 music_dir = getPath2(options.music_directory, music_subdir);
348 static char *getClassicArtworkSet(int type)
350 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
351 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
352 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
355 static char *getClassicArtworkDir(int type)
357 return (type == TREE_TYPE_GRAPHICS_DIR ?
358 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
359 type == TREE_TYPE_SOUNDS_DIR ?
360 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
361 type == TREE_TYPE_MUSIC_DIR ?
362 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
365 char *getUserGraphicsDir(void)
367 static char *usergraphics_dir = NULL;
369 if (usergraphics_dir == NULL)
370 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
372 return usergraphics_dir;
375 char *getUserSoundsDir(void)
377 static char *usersounds_dir = NULL;
379 if (usersounds_dir == NULL)
380 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
382 return usersounds_dir;
385 char *getUserMusicDir(void)
387 static char *usermusic_dir = NULL;
389 if (usermusic_dir == NULL)
390 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
392 return usermusic_dir;
395 static char *getSetupArtworkDir(TreeInfo *ti)
397 static char *artwork_dir = NULL;
402 checked_free(artwork_dir);
404 artwork_dir = getPath2(ti->basepath, ti->fullpath);
409 char *setLevelArtworkDir(TreeInfo *ti)
411 char **artwork_path_ptr, **artwork_set_ptr;
412 TreeInfo *level_artwork;
414 if (ti == NULL || leveldir_current == NULL)
417 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
418 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
420 checked_free(*artwork_path_ptr);
422 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
424 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
429 No (or non-existing) artwork configured in "levelinfo.conf". This would
430 normally result in using the artwork configured in the setup menu. But
431 if an artwork subdirectory exists (which might contain custom artwork
432 or an artwork configuration file), this level artwork must be treated
433 as relative to the default "classic" artwork, not to the artwork that
434 is currently configured in the setup menu.
436 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
437 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
438 the real "classic" artwork from the original R'n'D (like "gfx_classic").
441 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
443 checked_free(*artwork_set_ptr);
445 if (directoryExists(dir))
447 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
448 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
452 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
453 *artwork_set_ptr = NULL;
459 return *artwork_set_ptr;
462 static char *getLevelArtworkSet(int type)
464 if (leveldir_current == NULL)
467 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
470 static char *getLevelArtworkDir(int type)
472 if (leveldir_current == NULL)
473 return UNDEFINED_FILENAME;
475 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
478 char *getProgramMainDataPath(char *command_filename, char *base_path)
480 // check if the program's main data base directory is configured
481 if (!strEqual(base_path, "."))
482 return getStringCopy(base_path);
484 /* if the program is configured to start from current directory (default),
485 determine program package directory from program binary (some versions
486 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
487 set the current working directory to the program package directory) */
488 char *main_data_path = getBasePath(command_filename);
490 #if defined(PLATFORM_MACOSX)
491 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
493 char *main_data_path_old = main_data_path;
495 // cut relative path to Mac OS X application binary directory from path
496 main_data_path[strlen(main_data_path) -
497 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
499 // cut trailing path separator from path (but not if path is root directory)
500 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
501 main_data_path[strlen(main_data_path) - 1] = '\0';
503 // replace empty path with current directory
504 if (strEqual(main_data_path, ""))
505 main_data_path = ".";
507 // add relative path to Mac OS X application resources directory to path
508 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
510 free(main_data_path_old);
514 return main_data_path;
517 char *getProgramConfigFilename(char *command_filename)
519 static char *config_filename_1 = NULL;
520 static char *config_filename_2 = NULL;
521 static char *config_filename_3 = NULL;
522 static boolean initialized = FALSE;
526 char *command_filename_1 = getStringCopy(command_filename);
528 // strip trailing executable suffix from command filename
529 if (strSuffix(command_filename_1, ".exe"))
530 command_filename_1[strlen(command_filename_1) - 4] = '\0';
532 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
533 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
535 char *command_basepath = getBasePath(command_filename);
536 char *command_basename = getBaseNameNoSuffix(command_filename);
537 char *command_filename_2 = getPath2(command_basepath, command_basename);
539 config_filename_1 = getStringCat2(command_filename_1, ".conf");
540 config_filename_2 = getStringCat2(command_filename_2, ".conf");
541 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
543 checked_free(ro_base_path);
544 checked_free(conf_directory);
546 checked_free(command_basepath);
547 checked_free(command_basename);
549 checked_free(command_filename_1);
550 checked_free(command_filename_2);
555 // 1st try: look for config file that exactly matches the binary filename
556 if (fileExists(config_filename_1))
557 return config_filename_1;
559 // 2nd try: look for config file that matches binary filename without suffix
560 if (fileExists(config_filename_2))
561 return config_filename_2;
563 // 3rd try: return setup config filename in global program config directory
564 return config_filename_3;
567 char *getTapeFilename(int nr)
569 static char *filename = NULL;
570 char basename[MAX_FILENAME_LEN];
572 checked_free(filename);
574 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
575 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
580 char *getSolutionTapeFilename(int nr)
582 static char *filename = NULL;
583 char basename[MAX_FILENAME_LEN];
585 checked_free(filename);
587 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
588 filename = getPath2(getSolutionTapeDir(), basename);
590 if (!fileExists(filename))
592 static char *filename_sln = NULL;
594 checked_free(filename_sln);
596 sprintf(basename, "%03d.sln", nr);
597 filename_sln = getPath2(getSolutionTapeDir(), basename);
599 if (fileExists(filename_sln))
606 char *getScoreFilename(int nr)
608 static char *filename = NULL;
609 char basename[MAX_FILENAME_LEN];
611 checked_free(filename);
613 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
615 // used instead of "leveldir_current->subdir" (for network games)
616 filename = getPath2(getScoreDir(levelset.identifier), basename);
621 char *getSetupFilename(void)
623 static char *filename = NULL;
625 checked_free(filename);
627 filename = getPath2(getSetupDir(), SETUP_FILENAME);
632 char *getDefaultSetupFilename(void)
634 return program.config_filename;
637 char *getEditorSetupFilename(void)
639 static char *filename = NULL;
641 checked_free(filename);
642 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
644 if (fileExists(filename))
647 checked_free(filename);
648 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
653 char *getHelpAnimFilename(void)
655 static char *filename = NULL;
657 checked_free(filename);
659 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
664 char *getHelpTextFilename(void)
666 static char *filename = NULL;
668 checked_free(filename);
670 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
675 char *getLevelSetInfoFilename(void)
677 static char *filename = NULL;
692 for (i = 0; basenames[i] != NULL; i++)
694 checked_free(filename);
695 filename = getPath2(getCurrentLevelDir(), basenames[i]);
697 if (fileExists(filename))
704 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
706 static char basename[32];
708 sprintf(basename, "%s_%d.txt",
709 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
714 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
716 static char *filename = NULL;
718 boolean skip_setup_artwork = FALSE;
720 checked_free(filename);
722 basename = getLevelSetTitleMessageBasename(nr, initial);
724 if (!gfx.override_level_graphics)
726 // 1st try: look for special artwork in current level series directory
727 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
728 if (fileExists(filename))
733 // 2nd try: look for message file in current level set directory
734 filename = getPath2(getCurrentLevelDir(), basename);
735 if (fileExists(filename))
740 // check if there is special artwork configured in level series config
741 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
743 // 3rd try: look for special artwork configured in level series config
744 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
745 if (fileExists(filename))
750 // take missing artwork configured in level set config from default
751 skip_setup_artwork = TRUE;
755 if (!skip_setup_artwork)
757 // 4th try: look for special artwork in configured artwork directory
758 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
759 if (fileExists(filename))
765 // 5th try: look for default artwork in new default artwork directory
766 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
767 if (fileExists(filename))
772 // 6th try: look for default artwork in old default artwork directory
773 filename = getPath2(options.graphics_directory, basename);
774 if (fileExists(filename))
777 return NULL; // cannot find specified artwork file anywhere
780 static char *getCorrectedArtworkBasename(char *basename)
785 char *getCustomImageFilename(char *basename)
787 static char *filename = NULL;
788 boolean skip_setup_artwork = FALSE;
790 checked_free(filename);
792 basename = getCorrectedArtworkBasename(basename);
794 if (!gfx.override_level_graphics)
796 // 1st try: look for special artwork in current level series directory
797 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
798 if (fileExists(filename))
803 // check if there is special artwork configured in level series config
804 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
806 // 2nd try: look for special artwork configured in level series config
807 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
808 if (fileExists(filename))
813 // take missing artwork configured in level set config from default
814 skip_setup_artwork = TRUE;
818 if (!skip_setup_artwork)
820 // 3rd try: look for special artwork in configured artwork directory
821 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
822 if (fileExists(filename))
828 // 4th try: look for default artwork in new default artwork directory
829 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
830 if (fileExists(filename))
835 // 5th try: look for default artwork in old default artwork directory
836 filename = getImg2(options.graphics_directory, basename);
837 if (fileExists(filename))
840 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
844 Warn("cannot find artwork file '%s' (using fallback)", basename);
846 // 6th try: look for fallback artwork in old default artwork directory
847 // (needed to prevent errors when trying to access unused artwork files)
848 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
849 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 char *getCustomSoundFilename(char *basename)
858 static char *filename = NULL;
859 boolean skip_setup_artwork = FALSE;
861 checked_free(filename);
863 basename = getCorrectedArtworkBasename(basename);
865 if (!gfx.override_level_sounds)
867 // 1st try: look for special artwork in current level series directory
868 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
869 if (fileExists(filename))
874 // check if there is special artwork configured in level series config
875 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
877 // 2nd try: look for special artwork configured in level series config
878 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
879 if (fileExists(filename))
884 // take missing artwork configured in level set config from default
885 skip_setup_artwork = TRUE;
889 if (!skip_setup_artwork)
891 // 3rd try: look for special artwork in configured artwork directory
892 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
893 if (fileExists(filename))
899 // 4th try: look for default artwork in new default artwork directory
900 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
901 if (fileExists(filename))
906 // 5th try: look for default artwork in old default artwork directory
907 filename = getPath2(options.sounds_directory, basename);
908 if (fileExists(filename))
911 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
915 Warn("cannot find artwork file '%s' (using fallback)", basename);
917 // 6th try: look for fallback artwork in old default artwork directory
918 // (needed to prevent errors when trying to access unused artwork files)
919 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
920 if (fileExists(filename))
924 return NULL; // cannot find specified artwork file anywhere
927 char *getCustomMusicFilename(char *basename)
929 static char *filename = NULL;
930 boolean skip_setup_artwork = FALSE;
932 checked_free(filename);
934 basename = getCorrectedArtworkBasename(basename);
936 if (!gfx.override_level_music)
938 // 1st try: look for special artwork in current level series directory
939 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
940 if (fileExists(filename))
945 // check if there is special artwork configured in level series config
946 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
948 // 2nd try: look for special artwork configured in level series config
949 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
950 if (fileExists(filename))
955 // take missing artwork configured in level set config from default
956 skip_setup_artwork = TRUE;
960 if (!skip_setup_artwork)
962 // 3rd try: look for special artwork in configured artwork directory
963 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
964 if (fileExists(filename))
970 // 4th try: look for default artwork in new default artwork directory
971 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
972 if (fileExists(filename))
977 // 5th try: look for default artwork in old default artwork directory
978 filename = getPath2(options.music_directory, basename);
979 if (fileExists(filename))
982 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
986 Warn("cannot find artwork file '%s' (using fallback)", basename);
988 // 6th try: look for fallback artwork in old default artwork directory
989 // (needed to prevent errors when trying to access unused artwork files)
990 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
991 if (fileExists(filename))
995 return NULL; // cannot find specified artwork file anywhere
998 char *getCustomArtworkFilename(char *basename, int type)
1000 if (type == ARTWORK_TYPE_GRAPHICS)
1001 return getCustomImageFilename(basename);
1002 else if (type == ARTWORK_TYPE_SOUNDS)
1003 return getCustomSoundFilename(basename);
1004 else if (type == ARTWORK_TYPE_MUSIC)
1005 return getCustomMusicFilename(basename);
1007 return UNDEFINED_FILENAME;
1010 char *getCustomArtworkConfigFilename(int type)
1012 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1015 char *getCustomArtworkLevelConfigFilename(int type)
1017 static char *filename = NULL;
1019 checked_free(filename);
1021 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1026 char *getCustomMusicDirectory(void)
1028 static char *directory = NULL;
1029 boolean skip_setup_artwork = FALSE;
1031 checked_free(directory);
1033 if (!gfx.override_level_music)
1035 // 1st try: look for special artwork in current level series directory
1036 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1037 if (directoryExists(directory))
1042 // check if there is special artwork configured in level series config
1043 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1045 // 2nd try: look for special artwork configured in level series config
1046 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1047 if (directoryExists(directory))
1052 // take missing artwork configured in level set config from default
1053 skip_setup_artwork = TRUE;
1057 if (!skip_setup_artwork)
1059 // 3rd try: look for special artwork in configured artwork directory
1060 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1061 if (directoryExists(directory))
1067 // 4th try: look for default artwork in new default artwork directory
1068 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1069 if (directoryExists(directory))
1074 // 5th try: look for default artwork in old default artwork directory
1075 directory = getStringCopy(options.music_directory);
1076 if (directoryExists(directory))
1079 return NULL; // cannot find specified artwork file anywhere
1082 void InitTapeDirectory(char *level_subdir)
1084 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1085 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1086 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1089 void InitScoreDirectory(char *level_subdir)
1091 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1093 if (program.global_scores)
1094 createDirectory(getCommonDataDir(), "common data", permissions);
1096 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1098 createDirectory(getScoreDir(NULL), "main score", permissions);
1099 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1102 static void SaveUserLevelInfo(void);
1104 void InitUserLevelDirectory(char *level_subdir)
1106 if (!directoryExists(getUserLevelDir(level_subdir)))
1108 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1109 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1110 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1112 if (setup.internal.create_user_levelset)
1113 SaveUserLevelInfo();
1117 void InitNetworkLevelDirectory(char *level_subdir)
1119 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1121 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1122 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1123 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1124 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1128 void InitLevelSetupDirectory(char *level_subdir)
1130 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1131 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1132 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1135 static void InitCacheDirectory(void)
1137 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1138 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1142 // ----------------------------------------------------------------------------
1143 // some functions to handle lists of level and artwork directories
1144 // ----------------------------------------------------------------------------
1146 TreeInfo *newTreeInfo(void)
1148 return checked_calloc(sizeof(TreeInfo));
1151 TreeInfo *newTreeInfo_setDefaults(int type)
1153 TreeInfo *ti = newTreeInfo();
1155 setTreeInfoToDefaults(ti, type);
1160 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1162 node_new->next = *node_first;
1163 *node_first = node_new;
1166 void removeTreeInfo(TreeInfo **node_first)
1168 TreeInfo *node_old = *node_first;
1170 *node_first = node_old->next;
1171 node_old->next = NULL;
1173 freeTreeInfo(node_old);
1176 int numTreeInfo(TreeInfo *node)
1189 boolean validLevelSeries(TreeInfo *node)
1191 return (node != NULL && !node->node_group && !node->parent_link);
1194 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1199 if (node->node_group) // enter level group (step down into tree)
1200 return getFirstValidTreeInfoEntry(node->node_group);
1201 else if (node->parent_link) // skip start entry of level group
1203 if (node->next) // get first real level series entry
1204 return getFirstValidTreeInfoEntry(node->next);
1205 else // leave empty level group and go on
1206 return getFirstValidTreeInfoEntry(node->node_parent->next);
1208 else // this seems to be a regular level series
1212 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1217 if (node->node_parent == NULL) // top level group
1218 return *node->node_top;
1219 else // sub level group
1220 return node->node_parent->node_group;
1223 int numTreeInfoInGroup(TreeInfo *node)
1225 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1228 int getPosFromTreeInfo(TreeInfo *node)
1230 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1235 if (node_cmp == node)
1239 node_cmp = node_cmp->next;
1245 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1247 TreeInfo *node_default = node;
1259 return node_default;
1262 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1263 boolean include_node_groups)
1265 if (identifier == NULL)
1270 if (node->node_group)
1272 if (include_node_groups && strEqual(identifier, node->identifier))
1275 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1277 include_node_groups);
1281 else if (!node->parent_link)
1283 if (strEqual(identifier, node->identifier))
1293 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1295 return getTreeInfoFromIdentifierExt(node, identifier, FALSE);
1298 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1299 TreeInfo *node, boolean skip_sets_without_levels)
1306 if (!node->parent_link && !node->level_group &&
1307 skip_sets_without_levels && node->levels == 0)
1308 return cloneTreeNode(node_top, node_parent, node->next,
1309 skip_sets_without_levels);
1311 node_new = getTreeInfoCopy(node); // copy complete node
1313 node_new->node_top = node_top; // correct top node link
1314 node_new->node_parent = node_parent; // correct parent node link
1316 if (node->level_group)
1317 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1318 skip_sets_without_levels);
1320 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1321 skip_sets_without_levels);
1326 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1328 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1330 *ti_new = ti_cloned;
1333 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1335 boolean settings_changed = FALSE;
1339 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1340 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1341 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1342 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1343 char *graphics_set = NULL;
1345 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1346 graphics_set = node->graphics_set_ecs;
1348 if (node->graphics_set_aga && (want_aga || has_only_aga))
1349 graphics_set = node->graphics_set_aga;
1351 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1353 setString(&node->graphics_set, graphics_set);
1354 settings_changed = TRUE;
1357 if (node->node_group != NULL)
1358 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1363 return settings_changed;
1366 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1368 boolean settings_changed = FALSE;
1372 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1373 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1374 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1375 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1376 char *sounds_set = NULL;
1378 if (node->sounds_set_default && (want_default || has_only_default))
1379 sounds_set = node->sounds_set_default;
1381 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1382 sounds_set = node->sounds_set_lowpass;
1384 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1386 setString(&node->sounds_set, sounds_set);
1387 settings_changed = TRUE;
1390 if (node->node_group != NULL)
1391 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1396 return settings_changed;
1399 void dumpTreeInfo(TreeInfo *node, int depth)
1401 char bullet_list[] = { '-', '*', 'o' };
1405 Debug("tree", "Dumping TreeInfo:");
1409 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1411 for (i = 0; i < depth * 2; i++)
1412 DebugContinued("", " ");
1414 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1415 bullet, node->name, node->identifier,
1416 (node->node_parent ? node->node_parent->identifier : "-"),
1417 (node->node_group ? "[GROUP]" : ""));
1420 // use for dumping artwork info tree
1421 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1422 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1425 if (node->node_group != NULL)
1426 dumpTreeInfo(node->node_group, depth + 1);
1432 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1433 int (*compare_function)(const void *,
1436 int num_nodes = numTreeInfo(*node_first);
1437 TreeInfo **sort_array;
1438 TreeInfo *node = *node_first;
1444 // allocate array for sorting structure pointers
1445 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1447 // writing structure pointers to sorting array
1448 while (i < num_nodes && node) // double boundary check...
1450 sort_array[i] = node;
1456 // sorting the structure pointers in the sorting array
1457 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1460 // update the linkage of list elements with the sorted node array
1461 for (i = 0; i < num_nodes - 1; i++)
1462 sort_array[i]->next = sort_array[i + 1];
1463 sort_array[num_nodes - 1]->next = NULL;
1465 // update the linkage of the main list anchor pointer
1466 *node_first = sort_array[0];
1470 // now recursively sort the level group structures
1474 if (node->node_group != NULL)
1475 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1481 void sortTreeInfo(TreeInfo **node_first)
1483 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1487 // ============================================================================
1488 // some stuff from "files.c"
1489 // ============================================================================
1491 #if defined(PLATFORM_WIN32)
1493 #define S_IRGRP S_IRUSR
1496 #define S_IROTH S_IRUSR
1499 #define S_IWGRP S_IWUSR
1502 #define S_IWOTH S_IWUSR
1505 #define S_IXGRP S_IXUSR
1508 #define S_IXOTH S_IXUSR
1511 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1516 #endif // PLATFORM_WIN32
1518 // file permissions for newly written files
1519 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1520 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1521 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1523 #define MODE_W_PRIVATE (S_IWUSR)
1524 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1525 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1527 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1528 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1529 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1531 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1532 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1533 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1536 char *getHomeDir(void)
1538 static char *dir = NULL;
1540 #if defined(PLATFORM_WIN32)
1543 dir = checked_malloc(MAX_PATH + 1);
1545 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1548 #elif defined(PLATFORM_UNIX)
1551 if ((dir = getenv("HOME")) == NULL)
1553 dir = getUnixHomeDir();
1556 dir = getStringCopy(dir);
1568 char *getCommonDataDir(void)
1570 static char *common_data_dir = NULL;
1572 #if defined(PLATFORM_WIN32)
1573 if (common_data_dir == NULL)
1575 char *dir = checked_malloc(MAX_PATH + 1);
1577 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1578 && !strEqual(dir, "")) // empty for Windows 95/98
1579 common_data_dir = getPath2(dir, program.userdata_subdir);
1581 common_data_dir = options.rw_base_directory;
1584 if (common_data_dir == NULL)
1585 common_data_dir = options.rw_base_directory;
1588 return common_data_dir;
1591 char *getPersonalDataDir(void)
1593 static char *personal_data_dir = NULL;
1595 #if defined(PLATFORM_MACOSX)
1596 if (personal_data_dir == NULL)
1597 personal_data_dir = getPath2(getHomeDir(), "Documents");
1599 if (personal_data_dir == NULL)
1600 personal_data_dir = getHomeDir();
1603 return personal_data_dir;
1606 char *getMainUserGameDataDir(void)
1608 static char *main_user_data_dir = NULL;
1610 #if defined(PLATFORM_ANDROID)
1611 if (main_user_data_dir == NULL)
1612 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1613 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1614 SDL_AndroidGetExternalStoragePath() :
1615 SDL_AndroidGetInternalStoragePath());
1617 if (main_user_data_dir == NULL)
1618 main_user_data_dir = getPath2(getPersonalDataDir(),
1619 program.userdata_subdir);
1622 return main_user_data_dir;
1625 char *getUserGameDataDir(void)
1628 return getMainUserGameDataDir();
1630 return getUserDir(user.nr);
1633 char *getSetupDir(void)
1635 return getUserGameDataDir();
1638 static mode_t posix_umask(mode_t mask)
1640 #if defined(PLATFORM_UNIX)
1647 static int posix_mkdir(const char *pathname, mode_t mode)
1649 #if defined(PLATFORM_WIN32)
1650 return mkdir(pathname);
1652 return mkdir(pathname, mode);
1656 static boolean posix_process_running_setgid(void)
1658 #if defined(PLATFORM_UNIX)
1659 return (getgid() != getegid());
1665 void createDirectory(char *dir, char *text, int permission_class)
1667 if (directoryExists(dir))
1670 // leave "other" permissions in umask untouched, but ensure group parts
1671 // of USERDATA_DIR_MODE are not masked
1672 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1673 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1674 mode_t last_umask = posix_umask(0);
1675 mode_t group_umask = ~(dir_mode & S_IRWXG);
1676 int running_setgid = posix_process_running_setgid();
1678 if (permission_class == PERMS_PUBLIC)
1680 // if we're setgid, protect files against "other"
1681 // else keep umask(0) to make the dir world-writable
1684 posix_umask(last_umask & group_umask);
1686 dir_mode = DIR_PERMS_PUBLIC_ALL;
1689 if (posix_mkdir(dir, dir_mode) != 0)
1690 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1692 if (permission_class == PERMS_PUBLIC && !running_setgid)
1693 chmod(dir, dir_mode);
1695 posix_umask(last_umask); // restore previous umask
1698 void InitMainUserDataDirectory(void)
1700 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1703 void InitUserDataDirectory(void)
1705 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1709 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1710 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1714 void SetFilePermissions(char *filename, int permission_class)
1716 int running_setgid = posix_process_running_setgid();
1717 int perms = (permission_class == PERMS_PRIVATE ?
1718 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1720 if (permission_class == PERMS_PUBLIC && !running_setgid)
1721 perms = FILE_PERMS_PUBLIC_ALL;
1723 chmod(filename, perms);
1726 char *getCookie(char *file_type)
1728 static char cookie[MAX_COOKIE_LEN + 1];
1730 if (strlen(program.cookie_prefix) + 1 +
1731 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1732 return "[COOKIE ERROR]"; // should never happen
1734 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1735 program.cookie_prefix, file_type,
1736 program.version_super, program.version_major);
1741 void fprintFileHeader(FILE *file, char *basename)
1743 char *prefix = "# ";
1746 fprintf_line_with_prefix(file, prefix, sep1, 77);
1747 fprintf(file, "%s%s\n", prefix, basename);
1748 fprintf_line_with_prefix(file, prefix, sep1, 77);
1749 fprintf(file, "\n");
1752 int getFileVersionFromCookieString(const char *cookie)
1754 const char *ptr_cookie1, *ptr_cookie2;
1755 const char *pattern1 = "_FILE_VERSION_";
1756 const char *pattern2 = "?.?";
1757 const int len_cookie = strlen(cookie);
1758 const int len_pattern1 = strlen(pattern1);
1759 const int len_pattern2 = strlen(pattern2);
1760 const int len_pattern = len_pattern1 + len_pattern2;
1761 int version_super, version_major;
1763 if (len_cookie <= len_pattern)
1766 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1767 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1769 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1772 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1773 ptr_cookie2[1] != '.' ||
1774 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1777 version_super = ptr_cookie2[0] - '0';
1778 version_major = ptr_cookie2[2] - '0';
1780 return VERSION_IDENT(version_super, version_major, 0, 0);
1783 boolean checkCookieString(const char *cookie, const char *template)
1785 const char *pattern = "_FILE_VERSION_?.?";
1786 const int len_cookie = strlen(cookie);
1787 const int len_template = strlen(template);
1788 const int len_pattern = strlen(pattern);
1790 if (len_cookie != len_template)
1793 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1800 // ----------------------------------------------------------------------------
1801 // setup file list and hash handling functions
1802 // ----------------------------------------------------------------------------
1804 char *getFormattedSetupEntry(char *token, char *value)
1807 static char entry[MAX_LINE_LEN];
1809 // if value is an empty string, just return token without value
1813 // start with the token and some spaces to format output line
1814 sprintf(entry, "%s:", token);
1815 for (i = strlen(entry); i < token_value_position; i++)
1818 // continue with the token's value
1819 strcat(entry, value);
1824 SetupFileList *newSetupFileList(char *token, char *value)
1826 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1828 new->token = getStringCopy(token);
1829 new->value = getStringCopy(value);
1836 void freeSetupFileList(SetupFileList *list)
1841 checked_free(list->token);
1842 checked_free(list->value);
1845 freeSetupFileList(list->next);
1850 char *getListEntry(SetupFileList *list, char *token)
1855 if (strEqual(list->token, token))
1858 return getListEntry(list->next, token);
1861 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1866 if (strEqual(list->token, token))
1868 checked_free(list->value);
1870 list->value = getStringCopy(value);
1874 else if (list->next == NULL)
1875 return (list->next = newSetupFileList(token, value));
1877 return setListEntry(list->next, token, value);
1880 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1885 if (list->next == NULL)
1886 return (list->next = newSetupFileList(token, value));
1888 return addListEntry(list->next, token, value);
1891 #if ENABLE_UNUSED_CODE
1893 static void printSetupFileList(SetupFileList *list)
1898 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1899 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1901 printSetupFileList(list->next);
1907 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1908 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1909 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1910 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1912 #define insert_hash_entry hashtable_insert
1913 #define search_hash_entry hashtable_search
1914 #define change_hash_entry hashtable_change
1915 #define remove_hash_entry hashtable_remove
1918 unsigned int get_hash_from_key(void *key)
1923 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1924 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1925 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1926 it works better than many other constants, prime or not) has never been
1927 adequately explained.
1929 If you just want to have a good hash function, and cannot wait, djb2
1930 is one of the best string hash functions i know. It has excellent
1931 distribution and speed on many different sets of keys and table sizes.
1932 You are not likely to do better with one of the "well known" functions
1933 such as PJW, K&R, etc.
1935 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1938 char *str = (char *)key;
1939 unsigned int hash = 5381;
1942 while ((c = *str++))
1943 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1948 static int keys_are_equal(void *key1, void *key2)
1950 return (strEqual((char *)key1, (char *)key2));
1953 SetupFileHash *newSetupFileHash(void)
1955 SetupFileHash *new_hash =
1956 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1958 if (new_hash == NULL)
1959 Fail("create_hashtable() failed -- out of memory");
1964 void freeSetupFileHash(SetupFileHash *hash)
1969 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1972 char *getHashEntry(SetupFileHash *hash, char *token)
1977 return search_hash_entry(hash, token);
1980 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1987 value_copy = getStringCopy(value);
1989 // change value; if it does not exist, insert it as new
1990 if (!change_hash_entry(hash, token, value_copy))
1991 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1992 Fail("cannot insert into hash -- aborting");
1995 char *removeHashEntry(SetupFileHash *hash, char *token)
2000 return remove_hash_entry(hash, token);
2003 #if ENABLE_UNUSED_CODE
2005 static void printSetupFileHash(SetupFileHash *hash)
2007 BEGIN_HASH_ITERATION(hash, itr)
2009 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2010 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2012 END_HASH_ITERATION(hash, itr)
2017 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2018 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2019 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2021 static boolean token_value_separator_found = FALSE;
2022 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2023 static boolean token_value_separator_warning = FALSE;
2025 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2026 static boolean token_already_exists_warning = FALSE;
2029 static boolean getTokenValueFromSetupLineExt(char *line,
2030 char **token_ptr, char **value_ptr,
2031 char *filename, char *line_raw,
2033 boolean separator_required)
2035 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2036 char *token, *value, *line_ptr;
2038 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2039 if (line_raw == NULL)
2041 strncpy(line_copy, line, MAX_LINE_LEN);
2042 line_copy[MAX_LINE_LEN] = '\0';
2045 strcpy(line_raw_copy, line_copy);
2046 line_raw = line_raw_copy;
2049 // cut trailing comment from input line
2050 for (line_ptr = line; *line_ptr; line_ptr++)
2052 if (*line_ptr == '#')
2059 // cut trailing whitespaces from input line
2060 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2061 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2064 // ignore empty lines
2068 // cut leading whitespaces from token
2069 for (token = line; *token; token++)
2070 if (*token != ' ' && *token != '\t')
2073 // start with empty value as reliable default
2076 token_value_separator_found = FALSE;
2078 // find end of token to determine start of value
2079 for (line_ptr = token; *line_ptr; line_ptr++)
2081 // first look for an explicit token/value separator, like ':' or '='
2082 if (*line_ptr == ':' || *line_ptr == '=')
2084 *line_ptr = '\0'; // terminate token string
2085 value = line_ptr + 1; // set beginning of value
2087 token_value_separator_found = TRUE;
2093 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2094 // fallback: if no token/value separator found, also allow whitespaces
2095 if (!token_value_separator_found && !separator_required)
2097 for (line_ptr = token; *line_ptr; line_ptr++)
2099 if (*line_ptr == ' ' || *line_ptr == '\t')
2101 *line_ptr = '\0'; // terminate token string
2102 value = line_ptr + 1; // set beginning of value
2104 token_value_separator_found = TRUE;
2110 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2111 if (token_value_separator_found)
2113 if (!token_value_separator_warning)
2115 Debug("setup", "---");
2117 if (filename != NULL)
2119 Debug("setup", "missing token/value separator(s) in config file:");
2120 Debug("setup", "- config file: '%s'", filename);
2124 Debug("setup", "missing token/value separator(s):");
2127 token_value_separator_warning = TRUE;
2130 if (filename != NULL)
2131 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2133 Debug("setup", "- line: '%s'", line_raw);
2139 // cut trailing whitespaces from token
2140 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2141 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2144 // cut leading whitespaces from value
2145 for (; *value; value++)
2146 if (*value != ' ' && *value != '\t')
2155 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2157 // while the internal (old) interface does not require a token/value
2158 // separator (for downwards compatibility with existing files which
2159 // don't use them), it is mandatory for the external (new) interface
2161 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2164 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2165 boolean top_recursion_level, boolean is_hash)
2167 static SetupFileHash *include_filename_hash = NULL;
2168 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2169 char *token, *value, *line_ptr;
2170 void *insert_ptr = NULL;
2171 boolean read_continued_line = FALSE;
2173 int line_nr = 0, token_count = 0, include_count = 0;
2175 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2176 token_value_separator_warning = FALSE;
2179 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2180 token_already_exists_warning = FALSE;
2183 if (!(file = openFile(filename, MODE_READ)))
2185 #if DEBUG_NO_CONFIG_FILE
2186 Debug("setup", "cannot open configuration file '%s'", filename);
2192 // use "insert pointer" to store list end for constant insertion complexity
2194 insert_ptr = setup_file_data;
2196 // on top invocation, create hash to mark included files (to prevent loops)
2197 if (top_recursion_level)
2198 include_filename_hash = newSetupFileHash();
2200 // mark this file as already included (to prevent including it again)
2201 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2203 while (!checkEndOfFile(file))
2205 // read next line of input file
2206 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2209 // check if line was completely read and is terminated by line break
2210 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2213 // cut trailing line break (this can be newline and/or carriage return)
2214 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2215 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2218 // copy raw input line for later use (mainly debugging output)
2219 strcpy(line_raw, line);
2221 if (read_continued_line)
2223 // append new line to existing line, if there is enough space
2224 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2225 strcat(previous_line, line_ptr);
2227 strcpy(line, previous_line); // copy storage buffer to line
2229 read_continued_line = FALSE;
2232 // if the last character is '\', continue at next line
2233 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2235 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2236 strcpy(previous_line, line); // copy line to storage buffer
2238 read_continued_line = TRUE;
2243 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2244 line_raw, line_nr, FALSE))
2249 if (strEqual(token, "include"))
2251 if (getHashEntry(include_filename_hash, value) == NULL)
2253 char *basepath = getBasePath(filename);
2254 char *basename = getBaseName(value);
2255 char *filename_include = getPath2(basepath, basename);
2257 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2261 free(filename_include);
2267 Warn("ignoring already processed file '%s'", value);
2274 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2276 getHashEntry((SetupFileHash *)setup_file_data, token);
2278 if (old_value != NULL)
2280 if (!token_already_exists_warning)
2282 Debug("setup", "---");
2283 Debug("setup", "duplicate token(s) found in config file:");
2284 Debug("setup", "- config file: '%s'", filename);
2286 token_already_exists_warning = TRUE;
2289 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2290 Debug("setup", " old value: '%s'", old_value);
2291 Debug("setup", " new value: '%s'", value);
2295 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2299 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2309 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2310 if (token_value_separator_warning)
2311 Debug("setup", "---");
2314 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2315 if (token_already_exists_warning)
2316 Debug("setup", "---");
2319 if (token_count == 0 && include_count == 0)
2320 Warn("configuration file '%s' is empty", filename);
2322 if (top_recursion_level)
2323 freeSetupFileHash(include_filename_hash);
2328 static int compareSetupFileData(const void *object1, const void *object2)
2330 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2331 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2333 return strcmp(entry1->token, entry2->token);
2336 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2338 int item_count = hashtable_count(hash);
2339 int item_size = sizeof(struct ConfigInfo);
2340 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2344 // copy string pointers from hash to array
2345 BEGIN_HASH_ITERATION(hash, itr)
2347 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2348 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2352 if (i > item_count) // should never happen
2355 END_HASH_ITERATION(hash, itr)
2357 // sort string pointers from hash in array
2358 qsort(sort_array, item_count, item_size, compareSetupFileData);
2360 if (!(file = fopen(filename, MODE_WRITE)))
2362 Warn("cannot write configuration file '%s'", filename);
2367 for (i = 0; i < item_count; i++)
2368 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2369 sort_array[i].value));
2372 checked_free(sort_array);
2375 SetupFileList *loadSetupFileList(char *filename)
2377 SetupFileList *setup_file_list = newSetupFileList("", "");
2378 SetupFileList *first_valid_list_entry;
2380 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2382 freeSetupFileList(setup_file_list);
2387 first_valid_list_entry = setup_file_list->next;
2389 // free empty list header
2390 setup_file_list->next = NULL;
2391 freeSetupFileList(setup_file_list);
2393 return first_valid_list_entry;
2396 SetupFileHash *loadSetupFileHash(char *filename)
2398 SetupFileHash *setup_file_hash = newSetupFileHash();
2400 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2402 freeSetupFileHash(setup_file_hash);
2407 return setup_file_hash;
2411 // ============================================================================
2413 // ============================================================================
2415 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2416 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2417 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2418 #define TOKEN_STR_LAST_USER "last_user"
2420 // level directory info
2421 #define LEVELINFO_TOKEN_IDENTIFIER 0
2422 #define LEVELINFO_TOKEN_NAME 1
2423 #define LEVELINFO_TOKEN_NAME_SORTING 2
2424 #define LEVELINFO_TOKEN_AUTHOR 3
2425 #define LEVELINFO_TOKEN_YEAR 4
2426 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2427 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2428 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2429 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2430 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2431 #define LEVELINFO_TOKEN_TESTED_BY 10
2432 #define LEVELINFO_TOKEN_LEVELS 11
2433 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2434 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2435 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2436 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2437 #define LEVELINFO_TOKEN_READONLY 16
2438 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2439 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2440 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2441 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2442 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2443 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2444 #define LEVELINFO_TOKEN_MUSIC_SET 23
2445 #define LEVELINFO_TOKEN_FILENAME 24
2446 #define LEVELINFO_TOKEN_FILETYPE 25
2447 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2448 #define LEVELINFO_TOKEN_HANDICAP 27
2449 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2450 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2452 #define NUM_LEVELINFO_TOKENS 30
2454 static LevelDirTree ldi;
2456 static struct TokenInfo levelinfo_tokens[] =
2458 // level directory info
2459 { TYPE_STRING, &ldi.identifier, "identifier" },
2460 { TYPE_STRING, &ldi.name, "name" },
2461 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2462 { TYPE_STRING, &ldi.author, "author" },
2463 { TYPE_STRING, &ldi.year, "year" },
2464 { TYPE_STRING, &ldi.program_title, "program_title" },
2465 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2466 { TYPE_STRING, &ldi.program_company, "program_company" },
2467 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2468 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2469 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2470 { TYPE_INTEGER, &ldi.levels, "levels" },
2471 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2472 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2473 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2474 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2475 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2476 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2477 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2478 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2479 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2480 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2481 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2482 { TYPE_STRING, &ldi.music_set, "music_set" },
2483 { TYPE_STRING, &ldi.level_filename, "filename" },
2484 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2485 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2486 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2487 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2488 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2491 static struct TokenInfo artworkinfo_tokens[] =
2493 // artwork directory info
2494 { TYPE_STRING, &ldi.identifier, "identifier" },
2495 { TYPE_STRING, &ldi.subdir, "subdir" },
2496 { TYPE_STRING, &ldi.name, "name" },
2497 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2498 { TYPE_STRING, &ldi.author, "author" },
2499 { TYPE_STRING, &ldi.program_title, "program_title" },
2500 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2501 { TYPE_STRING, &ldi.program_company, "program_company" },
2502 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2503 { TYPE_STRING, &ldi.basepath, "basepath" },
2504 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2505 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2506 { TYPE_INTEGER, &ldi.color, "color" },
2507 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2512 static char *optional_tokens[] =
2515 "program_copyright",
2521 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2525 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2526 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2527 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2528 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2531 ti->node_parent = NULL;
2532 ti->node_group = NULL;
2539 ti->fullpath = NULL;
2540 ti->basepath = NULL;
2541 ti->identifier = NULL;
2542 ti->name = getStringCopy(ANONYMOUS_NAME);
2543 ti->name_sorting = NULL;
2544 ti->author = getStringCopy(ANONYMOUS_NAME);
2547 ti->program_title = NULL;
2548 ti->program_copyright = NULL;
2549 ti->program_company = NULL;
2551 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2552 ti->latest_engine = FALSE; // default: get from level
2553 ti->parent_link = FALSE;
2554 ti->in_user_dir = FALSE;
2555 ti->user_defined = FALSE;
2557 ti->class_desc = NULL;
2559 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2561 if (ti->type == TREE_TYPE_LEVEL_DIR)
2563 ti->imported_from = NULL;
2564 ti->imported_by = NULL;
2565 ti->tested_by = NULL;
2567 ti->graphics_set_ecs = NULL;
2568 ti->graphics_set_aga = NULL;
2569 ti->graphics_set = NULL;
2570 ti->sounds_set_default = NULL;
2571 ti->sounds_set_lowpass = NULL;
2572 ti->sounds_set = NULL;
2573 ti->music_set = NULL;
2574 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2575 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2576 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2578 ti->level_filename = NULL;
2579 ti->level_filetype = NULL;
2581 ti->special_flags = NULL;
2584 ti->first_level = 0;
2586 ti->level_group = FALSE;
2587 ti->handicap_level = 0;
2588 ti->readonly = TRUE;
2589 ti->handicap = TRUE;
2590 ti->skip_levels = FALSE;
2592 ti->use_emc_tiles = FALSE;
2596 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2600 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2602 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2607 // copy all values from the parent structure
2609 ti->type = parent->type;
2611 ti->node_top = parent->node_top;
2612 ti->node_parent = parent;
2613 ti->node_group = NULL;
2620 ti->fullpath = NULL;
2621 ti->basepath = NULL;
2622 ti->identifier = NULL;
2623 ti->name = getStringCopy(ANONYMOUS_NAME);
2624 ti->name_sorting = NULL;
2625 ti->author = getStringCopy(parent->author);
2626 ti->year = getStringCopy(parent->year);
2628 ti->program_title = getStringCopy(parent->program_title);
2629 ti->program_copyright = getStringCopy(parent->program_copyright);
2630 ti->program_company = getStringCopy(parent->program_company);
2632 ti->sort_priority = parent->sort_priority;
2633 ti->latest_engine = parent->latest_engine;
2634 ti->parent_link = FALSE;
2635 ti->in_user_dir = parent->in_user_dir;
2636 ti->user_defined = parent->user_defined;
2637 ti->color = parent->color;
2638 ti->class_desc = getStringCopy(parent->class_desc);
2640 ti->infotext = getStringCopy(parent->infotext);
2642 if (ti->type == TREE_TYPE_LEVEL_DIR)
2644 ti->imported_from = getStringCopy(parent->imported_from);
2645 ti->imported_by = getStringCopy(parent->imported_by);
2646 ti->tested_by = getStringCopy(parent->tested_by);
2648 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2649 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2650 ti->graphics_set = getStringCopy(parent->graphics_set);
2651 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2652 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2653 ti->sounds_set = getStringCopy(parent->sounds_set);
2654 ti->music_set = getStringCopy(parent->music_set);
2655 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2656 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2657 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2659 ti->level_filename = getStringCopy(parent->level_filename);
2660 ti->level_filetype = getStringCopy(parent->level_filetype);
2662 ti->special_flags = getStringCopy(parent->special_flags);
2664 ti->levels = parent->levels;
2665 ti->first_level = parent->first_level;
2666 ti->last_level = parent->last_level;
2667 ti->level_group = FALSE;
2668 ti->handicap_level = parent->handicap_level;
2669 ti->readonly = parent->readonly;
2670 ti->handicap = parent->handicap;
2671 ti->skip_levels = parent->skip_levels;
2673 ti->use_emc_tiles = parent->use_emc_tiles;
2677 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2679 TreeInfo *ti_copy = newTreeInfo();
2681 // copy all values from the original structure
2683 ti_copy->type = ti->type;
2685 ti_copy->node_top = ti->node_top;
2686 ti_copy->node_parent = ti->node_parent;
2687 ti_copy->node_group = ti->node_group;
2688 ti_copy->next = ti->next;
2690 ti_copy->cl_first = ti->cl_first;
2691 ti_copy->cl_cursor = ti->cl_cursor;
2693 ti_copy->subdir = getStringCopy(ti->subdir);
2694 ti_copy->fullpath = getStringCopy(ti->fullpath);
2695 ti_copy->basepath = getStringCopy(ti->basepath);
2696 ti_copy->identifier = getStringCopy(ti->identifier);
2697 ti_copy->name = getStringCopy(ti->name);
2698 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2699 ti_copy->author = getStringCopy(ti->author);
2700 ti_copy->year = getStringCopy(ti->year);
2702 ti_copy->program_title = getStringCopy(ti->program_title);
2703 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2704 ti_copy->program_company = getStringCopy(ti->program_company);
2706 ti_copy->imported_from = getStringCopy(ti->imported_from);
2707 ti_copy->imported_by = getStringCopy(ti->imported_by);
2708 ti_copy->tested_by = getStringCopy(ti->tested_by);
2710 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2711 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2712 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2713 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2714 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2715 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2716 ti_copy->music_set = getStringCopy(ti->music_set);
2717 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2718 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2719 ti_copy->music_path = getStringCopy(ti->music_path);
2721 ti_copy->level_filename = getStringCopy(ti->level_filename);
2722 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2724 ti_copy->special_flags = getStringCopy(ti->special_flags);
2726 ti_copy->levels = ti->levels;
2727 ti_copy->first_level = ti->first_level;
2728 ti_copy->last_level = ti->last_level;
2729 ti_copy->sort_priority = ti->sort_priority;
2731 ti_copy->latest_engine = ti->latest_engine;
2733 ti_copy->level_group = ti->level_group;
2734 ti_copy->parent_link = ti->parent_link;
2735 ti_copy->in_user_dir = ti->in_user_dir;
2736 ti_copy->user_defined = ti->user_defined;
2737 ti_copy->readonly = ti->readonly;
2738 ti_copy->handicap = ti->handicap;
2739 ti_copy->skip_levels = ti->skip_levels;
2741 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2743 ti_copy->color = ti->color;
2744 ti_copy->class_desc = getStringCopy(ti->class_desc);
2745 ti_copy->handicap_level = ti->handicap_level;
2747 ti_copy->infotext = getStringCopy(ti->infotext);
2752 void freeTreeInfo(TreeInfo *ti)
2757 checked_free(ti->subdir);
2758 checked_free(ti->fullpath);
2759 checked_free(ti->basepath);
2760 checked_free(ti->identifier);
2762 checked_free(ti->name);
2763 checked_free(ti->name_sorting);
2764 checked_free(ti->author);
2765 checked_free(ti->year);
2767 checked_free(ti->program_title);
2768 checked_free(ti->program_copyright);
2769 checked_free(ti->program_company);
2771 checked_free(ti->class_desc);
2773 checked_free(ti->infotext);
2775 if (ti->type == TREE_TYPE_LEVEL_DIR)
2777 checked_free(ti->imported_from);
2778 checked_free(ti->imported_by);
2779 checked_free(ti->tested_by);
2781 checked_free(ti->graphics_set_ecs);
2782 checked_free(ti->graphics_set_aga);
2783 checked_free(ti->graphics_set);
2784 checked_free(ti->sounds_set_default);
2785 checked_free(ti->sounds_set_lowpass);
2786 checked_free(ti->sounds_set);
2787 checked_free(ti->music_set);
2789 checked_free(ti->graphics_path);
2790 checked_free(ti->sounds_path);
2791 checked_free(ti->music_path);
2793 checked_free(ti->level_filename);
2794 checked_free(ti->level_filetype);
2796 checked_free(ti->special_flags);
2799 // recursively free child node
2801 freeTreeInfo(ti->node_group);
2803 // recursively free next node
2805 freeTreeInfo(ti->next);
2810 void setSetupInfo(struct TokenInfo *token_info,
2811 int token_nr, char *token_value)
2813 int token_type = token_info[token_nr].type;
2814 void *setup_value = token_info[token_nr].value;
2816 if (token_value == NULL)
2819 // set setup field to corresponding token value
2824 *(boolean *)setup_value = get_boolean_from_string(token_value);
2828 *(int *)setup_value = get_switch3_from_string(token_value);
2832 *(Key *)setup_value = getKeyFromKeyName(token_value);
2836 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2840 *(int *)setup_value = get_integer_from_string(token_value);
2844 checked_free(*(char **)setup_value);
2845 *(char **)setup_value = getStringCopy(token_value);
2849 *(int *)setup_value = get_player_nr_from_string(token_value);
2857 static int compareTreeInfoEntries(const void *object1, const void *object2)
2859 const TreeInfo *entry1 = *((TreeInfo **)object1);
2860 const TreeInfo *entry2 = *((TreeInfo **)object2);
2861 int tree_sorting1 = TREE_SORTING(entry1);
2862 int tree_sorting2 = TREE_SORTING(entry2);
2864 if (tree_sorting1 != tree_sorting2)
2865 return (tree_sorting1 - tree_sorting2);
2867 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2870 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2874 if (node_parent == NULL)
2877 ti_new = newTreeInfo();
2878 setTreeInfoToDefaults(ti_new, node_parent->type);
2880 ti_new->node_parent = node_parent;
2881 ti_new->parent_link = TRUE;
2883 setString(&ti_new->identifier, node_parent->identifier);
2884 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2885 setString(&ti_new->name_sorting, ti_new->name);
2887 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2888 setString(&ti_new->fullpath, node_parent->fullpath);
2890 ti_new->sort_priority = node_parent->sort_priority;
2891 ti_new->latest_engine = node_parent->latest_engine;
2893 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2895 pushTreeInfo(&node_parent->node_group, ti_new);
2900 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2902 if (node_first == NULL)
2905 TreeInfo *ti_new = newTreeInfo();
2906 int type = node_first->type;
2908 setTreeInfoToDefaults(ti_new, type);
2910 ti_new->node_parent = NULL;
2911 ti_new->parent_link = FALSE;
2913 setString(&ti_new->identifier, node_first->identifier);
2914 setString(&ti_new->name, TREE_INFOTEXT(type));
2915 setString(&ti_new->name_sorting, ti_new->name);
2917 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2918 setString(&ti_new->fullpath, ".");
2920 ti_new->sort_priority = node_first->sort_priority;;
2921 ti_new->latest_engine = node_first->latest_engine;
2923 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2925 ti_new->node_group = node_first;
2926 ti_new->level_group = TRUE;
2928 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2930 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2931 setString(&ti_new2->name_sorting, ti_new2->name);
2936 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2940 if (node->node_group)
2941 setTreeInfoParentNodes(node->node_group, node);
2943 node->node_parent = node_parent;
2950 // ----------------------------------------------------------------------------
2951 // functions for handling level and custom artwork info cache
2952 // ----------------------------------------------------------------------------
2954 static void LoadArtworkInfoCache(void)
2956 InitCacheDirectory();
2958 if (artworkinfo_cache_old == NULL)
2960 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2962 // try to load artwork info hash from already existing cache file
2963 artworkinfo_cache_old = loadSetupFileHash(filename);
2965 // if no artwork info cache file was found, start with empty hash
2966 if (artworkinfo_cache_old == NULL)
2967 artworkinfo_cache_old = newSetupFileHash();
2972 if (artworkinfo_cache_new == NULL)
2973 artworkinfo_cache_new = newSetupFileHash();
2975 update_artworkinfo_cache = FALSE;
2978 static void SaveArtworkInfoCache(void)
2980 if (!update_artworkinfo_cache)
2983 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2985 InitCacheDirectory();
2987 saveSetupFileHash(artworkinfo_cache_new, filename);
2992 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2994 static char *prefix = NULL;
2996 checked_free(prefix);
2998 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3003 // (identical to above function, but separate string buffer needed -- nasty)
3004 static char *getCacheToken(char *prefix, char *suffix)
3006 static char *token = NULL;
3008 checked_free(token);
3010 token = getStringCat2WithSeparator(prefix, suffix, ".");
3015 static char *getFileTimestampString(char *filename)
3017 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3020 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3022 struct stat file_status;
3024 if (timestamp_string == NULL)
3027 if (!fileExists(filename)) // file does not exist
3028 return (atoi(timestamp_string) != 0);
3030 if (stat(filename, &file_status) != 0) // cannot stat file
3033 return (file_status.st_mtime != atoi(timestamp_string));
3036 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3038 char *identifier = level_node->subdir;
3039 char *type_string = ARTWORK_DIRECTORY(type);
3040 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3041 char *token_main = getCacheToken(token_prefix, "CACHED");
3042 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3043 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3044 TreeInfo *artwork_info = NULL;
3046 if (!use_artworkinfo_cache)
3049 if (optional_tokens_hash == NULL)
3053 // create hash from list of optional tokens (for quick access)
3054 optional_tokens_hash = newSetupFileHash();
3055 for (i = 0; optional_tokens[i] != NULL; i++)
3056 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3063 artwork_info = newTreeInfo();
3064 setTreeInfoToDefaults(artwork_info, type);
3066 // set all structure fields according to the token/value pairs
3067 ldi = *artwork_info;
3068 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3070 char *token_suffix = artworkinfo_tokens[i].text;
3071 char *token = getCacheToken(token_prefix, token_suffix);
3072 char *value = getHashEntry(artworkinfo_cache_old, token);
3074 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3076 setSetupInfo(artworkinfo_tokens, i, value);
3078 // check if cache entry for this item is mandatory, but missing
3079 if (value == NULL && !optional)
3081 Warn("missing cache entry '%s'", token);
3087 *artwork_info = ldi;
3092 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3093 LEVELINFO_FILENAME);
3094 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3095 ARTWORKINFO_FILENAME(type));
3097 // check if corresponding "levelinfo.conf" file has changed
3098 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3099 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3101 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3104 // check if corresponding "<artworkinfo>.conf" file has changed
3105 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3106 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3108 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3111 checked_free(filename_levelinfo);
3112 checked_free(filename_artworkinfo);
3115 if (!cached && artwork_info != NULL)
3117 freeTreeInfo(artwork_info);
3122 return artwork_info;
3125 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3126 LevelDirTree *level_node, int type)
3128 char *identifier = level_node->subdir;
3129 char *type_string = ARTWORK_DIRECTORY(type);
3130 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3131 char *token_main = getCacheToken(token_prefix, "CACHED");
3132 boolean set_cache_timestamps = TRUE;
3135 setHashEntry(artworkinfo_cache_new, token_main, "true");
3137 if (set_cache_timestamps)
3139 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3140 LEVELINFO_FILENAME);
3141 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3142 ARTWORKINFO_FILENAME(type));
3143 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3144 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3146 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3147 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3149 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3150 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3152 checked_free(filename_levelinfo);
3153 checked_free(filename_artworkinfo);
3154 checked_free(timestamp_levelinfo);
3155 checked_free(timestamp_artworkinfo);
3158 ldi = *artwork_info;
3159 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3161 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3162 char *value = getSetupValue(artworkinfo_tokens[i].type,
3163 artworkinfo_tokens[i].value);
3165 setHashEntry(artworkinfo_cache_new, token, value);
3170 // ----------------------------------------------------------------------------
3171 // functions for loading level info and custom artwork info
3172 // ----------------------------------------------------------------------------
3174 int GetZipFileTreeType(char *zip_filename)
3176 static char *top_dir_path = NULL;
3177 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3178 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3180 GRAPHICSINFO_FILENAME,
3181 SOUNDSINFO_FILENAME,
3187 checked_free(top_dir_path);
3188 top_dir_path = NULL;
3190 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3192 checked_free(top_dir_conf_filename[j]);
3193 top_dir_conf_filename[j] = NULL;
3196 char **zip_entries = zip_list(zip_filename);
3198 // check if zip file successfully opened
3199 if (zip_entries == NULL || zip_entries[0] == NULL)
3200 return TREE_TYPE_UNDEFINED;
3202 // first zip file entry is expected to be top level directory
3203 char *top_dir = zip_entries[0];
3205 // check if valid top level directory found in zip file
3206 if (!strSuffix(top_dir, "/"))
3207 return TREE_TYPE_UNDEFINED;
3209 // get filenames of valid configuration files in top level directory
3210 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3211 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3213 int tree_type = TREE_TYPE_UNDEFINED;
3216 while (zip_entries[e] != NULL)
3218 // check if every zip file entry is below top level directory
3219 if (!strPrefix(zip_entries[e], top_dir))
3220 return TREE_TYPE_UNDEFINED;
3222 // check if this zip file entry is a valid configuration filename
3223 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3225 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3227 // only exactly one valid configuration file allowed
3228 if (tree_type != TREE_TYPE_UNDEFINED)
3229 return TREE_TYPE_UNDEFINED;
3241 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3244 static char *top_dir_path = NULL;
3245 static char *top_dir_conf_filename = NULL;
3247 checked_free(top_dir_path);
3248 checked_free(top_dir_conf_filename);
3250 top_dir_path = NULL;
3251 top_dir_conf_filename = NULL;
3253 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3254 ARTWORKINFO_FILENAME(tree_type));
3256 // check if valid configuration filename determined
3257 if (conf_basename == NULL || strEqual(conf_basename, ""))
3260 char **zip_entries = zip_list(zip_filename);
3262 // check if zip file successfully opened
3263 if (zip_entries == NULL || zip_entries[0] == NULL)
3266 // first zip file entry is expected to be top level directory
3267 char *top_dir = zip_entries[0];
3269 // check if valid top level directory found in zip file
3270 if (!strSuffix(top_dir, "/"))
3273 // get path of extracted top level directory
3274 top_dir_path = getPath2(directory, top_dir);
3276 // remove trailing directory separator from top level directory path
3277 // (required to be able to check for file and directory in next step)
3278 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3280 // check if zip file's top level directory already exists in target directory
3281 if (fileExists(top_dir_path)) // (checks for file and directory)
3284 // get filename of configuration file in top level directory
3285 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3287 boolean found_top_dir_conf_filename = FALSE;
3290 while (zip_entries[i] != NULL)
3292 // check if every zip file entry is below top level directory
3293 if (!strPrefix(zip_entries[i], top_dir))
3296 // check if this zip file entry is the configuration filename
3297 if (strEqual(zip_entries[i], top_dir_conf_filename))
3298 found_top_dir_conf_filename = TRUE;
3303 // check if valid configuration filename was found in zip file
3304 if (!found_top_dir_conf_filename)
3310 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3313 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3316 if (!zip_file_valid)
3318 Warn("zip file '%s' rejected!", zip_filename);
3323 char **zip_entries = zip_extract(zip_filename, directory);
3325 if (zip_entries == NULL)
3327 Warn("zip file '%s' could not be extracted!", zip_filename);
3332 Info("zip file '%s' successfully extracted!", zip_filename);
3334 // first zip file entry contains top level directory
3335 char *top_dir = zip_entries[0];
3337 // remove trailing directory separator from top level directory
3338 top_dir[strlen(top_dir) - 1] = '\0';
3343 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3346 DirectoryEntry *dir_entry;
3348 if ((dir = openDirectory(directory)) == NULL)
3350 // display error if directory is main "options.graphics_directory" etc.
3351 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3352 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3353 Warn("cannot read directory '%s'", directory);
3358 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3360 // skip non-zip files (and also directories with zip extension)
3361 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3364 char *zip_filename = getPath2(directory, dir_entry->basename);
3365 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3366 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3368 // check if zip file hasn't already been extracted or rejected
3369 if (!fileExists(zip_filename_extracted) &&
3370 !fileExists(zip_filename_rejected))
3372 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3374 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3375 zip_filename_rejected);
3378 // create empty file to mark zip file as extracted or rejected
3379 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3380 fclose(marker_file);
3383 free(zip_filename_extracted);
3384 free(zip_filename_rejected);
3388 closeDirectory(dir);
3391 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3392 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3394 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3395 TreeInfo *node_parent,
3396 char *level_directory,
3397 char *directory_name)
3399 char *directory_path = getPath2(level_directory, directory_name);
3400 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3401 SetupFileHash *setup_file_hash;
3402 LevelDirTree *leveldir_new = NULL;
3405 // unless debugging, silently ignore directories without "levelinfo.conf"
3406 if (!options.debug && !fileExists(filename))
3408 free(directory_path);
3414 setup_file_hash = loadSetupFileHash(filename);
3416 if (setup_file_hash == NULL)
3418 #if DEBUG_NO_CONFIG_FILE
3419 Debug("setup", "ignoring level directory '%s'", directory_path);
3422 free(directory_path);
3428 leveldir_new = newTreeInfo();
3431 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3433 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3435 leveldir_new->subdir = getStringCopy(directory_name);
3437 // set all structure fields according to the token/value pairs
3438 ldi = *leveldir_new;
3439 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3440 setSetupInfo(levelinfo_tokens, i,
3441 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3442 *leveldir_new = ldi;
3444 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3445 setString(&leveldir_new->name, leveldir_new->subdir);
3447 if (leveldir_new->identifier == NULL)
3448 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3450 if (leveldir_new->name_sorting == NULL)
3451 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3453 if (node_parent == NULL) // top level group
3455 leveldir_new->basepath = getStringCopy(level_directory);
3456 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3458 else // sub level group
3460 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3461 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3464 leveldir_new->last_level =
3465 leveldir_new->first_level + leveldir_new->levels - 1;
3467 leveldir_new->in_user_dir =
3468 (!strEqual(leveldir_new->basepath, options.level_directory));
3470 // adjust some settings if user's private level directory was detected
3471 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3472 leveldir_new->in_user_dir &&
3473 (strEqual(leveldir_new->subdir, getLoginName()) ||
3474 strEqual(leveldir_new->name, getLoginName()) ||
3475 strEqual(leveldir_new->author, getRealName())))
3477 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3478 leveldir_new->readonly = FALSE;
3481 leveldir_new->user_defined =
3482 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3484 leveldir_new->color = LEVELCOLOR(leveldir_new);
3486 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3488 leveldir_new->handicap_level = // set handicap to default value
3489 (leveldir_new->user_defined || !leveldir_new->handicap ?
3490 leveldir_new->last_level : leveldir_new->first_level);
3492 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3494 pushTreeInfo(node_first, leveldir_new);
3496 freeSetupFileHash(setup_file_hash);
3498 if (leveldir_new->level_group)
3500 // create node to link back to current level directory
3501 createParentTreeInfoNode(leveldir_new);
3503 // recursively step into sub-directory and look for more level series
3504 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3505 leveldir_new, directory_path);
3508 free(directory_path);
3514 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3515 TreeInfo *node_parent,
3516 char *level_directory)
3518 // ---------- 1st stage: process any level set zip files ----------
3520 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3522 // ---------- 2nd stage: check for level set directories ----------
3525 DirectoryEntry *dir_entry;
3526 boolean valid_entry_found = FALSE;
3528 if ((dir = openDirectory(level_directory)) == NULL)
3530 Warn("cannot read level directory '%s'", level_directory);
3535 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3537 char *directory_name = dir_entry->basename;
3538 char *directory_path = getPath2(level_directory, directory_name);
3540 // skip entries for current and parent directory
3541 if (strEqual(directory_name, ".") ||
3542 strEqual(directory_name, ".."))
3544 free(directory_path);
3549 // find out if directory entry is itself a directory
3550 if (!dir_entry->is_directory) // not a directory
3552 free(directory_path);
3557 free(directory_path);
3559 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3560 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3561 strEqual(directory_name, MUSIC_DIRECTORY))
3564 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3569 closeDirectory(dir);
3571 // special case: top level directory may directly contain "levelinfo.conf"
3572 if (node_parent == NULL && !valid_entry_found)
3574 // check if this directory directly contains a file "levelinfo.conf"
3575 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3576 level_directory, ".");
3579 if (!valid_entry_found)
3580 Warn("cannot find any valid level series in directory '%s'",
3584 boolean AdjustGraphicsForEMC(void)
3586 boolean settings_changed = FALSE;
3588 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3589 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3591 return settings_changed;
3594 boolean AdjustSoundsForEMC(void)
3596 boolean settings_changed = FALSE;
3598 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3599 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3601 return settings_changed;
3604 void LoadLevelInfo(void)
3606 InitUserLevelDirectory(getLoginName());
3608 DrawInitText("Loading level series", 120, FC_GREEN);
3610 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3611 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3613 leveldir_first = createTopTreeInfoNode(leveldir_first);
3615 /* after loading all level set information, clone the level directory tree
3616 and remove all level sets without levels (these may still contain artwork
3617 to be offered in the setup menu as "custom artwork", and are therefore
3618 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3619 leveldir_first_all = leveldir_first;
3620 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3622 AdjustGraphicsForEMC();
3623 AdjustSoundsForEMC();
3625 // before sorting, the first entries will be from the user directory
3626 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3628 if (leveldir_first == NULL)
3629 Fail("cannot find any valid level series in any directory");
3631 sortTreeInfo(&leveldir_first);
3633 #if ENABLE_UNUSED_CODE
3634 dumpTreeInfo(leveldir_first, 0);
3638 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3639 TreeInfo *node_parent,
3640 char *base_directory,
3641 char *directory_name, int type)
3643 char *directory_path = getPath2(base_directory, directory_name);
3644 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3645 SetupFileHash *setup_file_hash = NULL;
3646 TreeInfo *artwork_new = NULL;
3649 if (fileExists(filename))
3650 setup_file_hash = loadSetupFileHash(filename);
3652 if (setup_file_hash == NULL) // no config file -- look for artwork files
3655 DirectoryEntry *dir_entry;
3656 boolean valid_file_found = FALSE;
3658 if ((dir = openDirectory(directory_path)) != NULL)
3660 while ((dir_entry = readDirectory(dir)) != NULL)
3662 if (FileIsArtworkType(dir_entry->filename, type))
3664 valid_file_found = TRUE;
3670 closeDirectory(dir);
3673 if (!valid_file_found)
3675 #if DEBUG_NO_CONFIG_FILE
3676 if (!strEqual(directory_name, "."))
3677 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3680 free(directory_path);
3687 artwork_new = newTreeInfo();
3690 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3692 setTreeInfoToDefaults(artwork_new, type);
3694 artwork_new->subdir = getStringCopy(directory_name);
3696 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3698 // set all structure fields according to the token/value pairs
3700 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3701 setSetupInfo(levelinfo_tokens, i,
3702 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3705 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3706 setString(&artwork_new->name, artwork_new->subdir);
3708 if (artwork_new->identifier == NULL)
3709 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3711 if (artwork_new->name_sorting == NULL)
3712 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3715 if (node_parent == NULL) // top level group
3717 artwork_new->basepath = getStringCopy(base_directory);
3718 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3720 else // sub level group
3722 artwork_new->basepath = getStringCopy(node_parent->basepath);
3723 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3726 artwork_new->in_user_dir =
3727 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3729 // (may use ".sort_priority" from "setup_file_hash" above)
3730 artwork_new->color = ARTWORKCOLOR(artwork_new);
3732 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3734 if (setup_file_hash == NULL) // (after determining ".user_defined")
3736 if (strEqual(artwork_new->subdir, "."))
3738 if (artwork_new->user_defined)
3740 setString(&artwork_new->identifier, "private");
3741 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3745 setString(&artwork_new->identifier, "classic");
3746 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3749 // set to new values after changing ".sort_priority"
3750 artwork_new->color = ARTWORKCOLOR(artwork_new);
3752 setString(&artwork_new->class_desc,
3753 getLevelClassDescription(artwork_new));
3757 setString(&artwork_new->identifier, artwork_new->subdir);
3760 setString(&artwork_new->name, artwork_new->identifier);
3761 setString(&artwork_new->name_sorting, artwork_new->name);
3764 pushTreeInfo(node_first, artwork_new);
3766 freeSetupFileHash(setup_file_hash);
3768 free(directory_path);
3774 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3775 TreeInfo *node_parent,
3776 char *base_directory, int type)
3778 // ---------- 1st stage: process any artwork set zip files ----------
3780 ProcessZipFilesInDirectory(base_directory, type);
3782 // ---------- 2nd stage: check for artwork set directories ----------
3785 DirectoryEntry *dir_entry;
3786 boolean valid_entry_found = FALSE;
3788 if ((dir = openDirectory(base_directory)) == NULL)
3790 // display error if directory is main "options.graphics_directory" etc.
3791 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3792 Warn("cannot read directory '%s'", base_directory);
3797 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3799 char *directory_name = dir_entry->basename;
3800 char *directory_path = getPath2(base_directory, directory_name);
3802 // skip directory entries for current and parent directory
3803 if (strEqual(directory_name, ".") ||
3804 strEqual(directory_name, ".."))
3806 free(directory_path);
3811 // skip directory entries which are not a directory
3812 if (!dir_entry->is_directory) // not a directory
3814 free(directory_path);
3819 free(directory_path);
3821 // check if this directory contains artwork with or without config file
3822 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3824 directory_name, type);
3827 closeDirectory(dir);
3829 // check if this directory directly contains artwork itself
3830 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3831 base_directory, ".",
3833 if (!valid_entry_found)
3834 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3837 static TreeInfo *getDummyArtworkInfo(int type)
3839 // this is only needed when there is completely no artwork available
3840 TreeInfo *artwork_new = newTreeInfo();
3842 setTreeInfoToDefaults(artwork_new, type);
3844 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3845 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3846 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3848 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3849 setString(&artwork_new->name, UNDEFINED_FILENAME);
3850 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3855 void SetCurrentArtwork(int type)
3857 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3858 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3859 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3860 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3862 // set current artwork to artwork configured in setup menu
3863 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3865 // if not found, set current artwork to default artwork
3866 if (*current_ptr == NULL)
3867 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3869 // if not found, set current artwork to first artwork in tree
3870 if (*current_ptr == NULL)
3871 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3874 void ChangeCurrentArtworkIfNeeded(int type)
3876 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3877 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3879 if (!strEqual(current_identifier, setup_set))
3880 SetCurrentArtwork(type);
3883 void LoadArtworkInfo(void)
3885 LoadArtworkInfoCache();
3887 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3889 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3890 options.graphics_directory,
3891 TREE_TYPE_GRAPHICS_DIR);
3892 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3893 getUserGraphicsDir(),
3894 TREE_TYPE_GRAPHICS_DIR);
3896 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3897 options.sounds_directory,
3898 TREE_TYPE_SOUNDS_DIR);
3899 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3901 TREE_TYPE_SOUNDS_DIR);
3903 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3904 options.music_directory,
3905 TREE_TYPE_MUSIC_DIR);
3906 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3908 TREE_TYPE_MUSIC_DIR);
3910 if (artwork.gfx_first == NULL)
3911 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3912 if (artwork.snd_first == NULL)
3913 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3914 if (artwork.mus_first == NULL)
3915 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3917 // before sorting, the first entries will be from the user directory
3918 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3919 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3920 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3922 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3923 artwork.snd_current_identifier = artwork.snd_current->identifier;
3924 artwork.mus_current_identifier = artwork.mus_current->identifier;
3926 #if ENABLE_UNUSED_CODE
3927 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3928 artwork.gfx_current_identifier);
3929 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3930 artwork.snd_current_identifier);
3931 Debug("setup:LoadArtworkInfo", "music set == %s",
3932 artwork.mus_current_identifier);
3935 sortTreeInfo(&artwork.gfx_first);
3936 sortTreeInfo(&artwork.snd_first);
3937 sortTreeInfo(&artwork.mus_first);
3939 #if ENABLE_UNUSED_CODE
3940 dumpTreeInfo(artwork.gfx_first, 0);
3941 dumpTreeInfo(artwork.snd_first, 0);
3942 dumpTreeInfo(artwork.mus_first, 0);
3946 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3948 ArtworkDirTree *artwork_new = newTreeInfo();
3949 char *top_node_name = "standalone artwork";
3951 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3953 artwork_new->level_group = TRUE;
3955 setString(&artwork_new->identifier, top_node_name);
3956 setString(&artwork_new->name, top_node_name);
3957 setString(&artwork_new->name_sorting, top_node_name);
3959 // create node to link back to current custom artwork directory
3960 createParentTreeInfoNode(artwork_new);
3962 // move existing custom artwork tree into newly created sub-tree
3963 artwork_new->node_group->next = *artwork_node;
3965 // change custom artwork tree to contain only newly created node
3966 *artwork_node = artwork_new;
3969 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3970 ArtworkDirTree *node_parent,
3971 LevelDirTree *level_node,
3972 boolean empty_level_set_mode)
3974 int type = (*artwork_node)->type;
3976 // recursively check all level directories for artwork sub-directories
3980 boolean empty_level_set = (level_node->levels == 0);
3982 // check all tree entries for artwork, but skip parent link entries
3983 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3985 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3986 boolean cached = (artwork_new != NULL);
3990 pushTreeInfo(artwork_node, artwork_new);
3994 TreeInfo *topnode_last = *artwork_node;
3995 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3996 ARTWORK_DIRECTORY(type));
3998 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4000 if (topnode_last != *artwork_node) // check for newly added node
4002 artwork_new = *artwork_node;
4004 setString(&artwork_new->identifier, level_node->subdir);
4005 setString(&artwork_new->name, level_node->name);
4006 setString(&artwork_new->name_sorting, level_node->name_sorting);
4008 artwork_new->sort_priority = level_node->sort_priority;
4009 artwork_new->in_user_dir = level_node->in_user_dir;
4010 artwork_new->color = LEVELCOLOR(artwork_new);
4012 update_artworkinfo_cache = TRUE;
4018 // insert artwork info (from old cache or filesystem) into new cache
4019 if (artwork_new != NULL)
4020 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4023 DrawInitText(level_node->name, 150, FC_YELLOW);
4025 if (level_node->node_group != NULL)
4027 TreeInfo *artwork_new = newTreeInfo();
4030 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4032 setTreeInfoToDefaults(artwork_new, type);
4034 artwork_new->level_group = TRUE;
4036 setString(&artwork_new->identifier, level_node->subdir);
4038 if (node_parent == NULL) // check for top tree node
4040 char *top_node_name = (empty_level_set_mode ?
4041 "artwork for certain level sets" :
4042 "artwork included in level sets");
4044 setString(&artwork_new->name, top_node_name);
4045 setString(&artwork_new->name_sorting, top_node_name);
4049 setString(&artwork_new->name, level_node->name);
4050 setString(&artwork_new->name_sorting, level_node->name_sorting);
4053 pushTreeInfo(artwork_node, artwork_new);
4055 // create node to link back to current custom artwork directory
4056 createParentTreeInfoNode(artwork_new);
4058 // recursively step into sub-directory and look for more custom artwork
4059 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4060 level_node->node_group,
4061 empty_level_set_mode);
4063 // if sub-tree has no custom artwork at all, remove it
4064 if (artwork_new->node_group->next == NULL)
4065 removeTreeInfo(artwork_node);
4068 level_node = level_node->next;
4072 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4074 // move peviously loaded artwork tree into separate sub-tree
4075 MoveArtworkInfoIntoSubTree(artwork_node);
4077 // load artwork from level sets into separate sub-trees
4078 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4079 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4081 // add top tree node over all three separate sub-trees
4082 *artwork_node = createTopTreeInfoNode(*artwork_node);
4084 // set all parent links (back links) in complete artwork tree
4085 setTreeInfoParentNodes(*artwork_node, NULL);
4088 void LoadLevelArtworkInfo(void)
4090 print_timestamp_init("LoadLevelArtworkInfo");
4092 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4094 print_timestamp_time("DrawTimeText");
4096 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4097 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4098 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4099 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4100 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4101 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4103 SaveArtworkInfoCache();
4105 print_timestamp_time("SaveArtworkInfoCache");
4107 // needed for reloading level artwork not known at ealier stage
4108 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4109 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4110 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4112 print_timestamp_time("getTreeInfoFromIdentifier");
4114 sortTreeInfo(&artwork.gfx_first);
4115 sortTreeInfo(&artwork.snd_first);
4116 sortTreeInfo(&artwork.mus_first);
4118 print_timestamp_time("sortTreeInfo");
4120 #if ENABLE_UNUSED_CODE
4121 dumpTreeInfo(artwork.gfx_first, 0);
4122 dumpTreeInfo(artwork.snd_first, 0);
4123 dumpTreeInfo(artwork.mus_first, 0);
4126 print_timestamp_done("LoadLevelArtworkInfo");
4129 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4130 char *tree_subdir_new, int type)
4132 if (tree_node_old == NULL)
4134 if (type == TREE_TYPE_LEVEL_DIR)
4136 // get level info tree node of personal user level set
4137 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4139 // this may happen if "setup.internal.create_user_levelset" is FALSE
4140 // or if file "levelinfo.conf" is missing in personal user level set
4141 if (tree_node_old == NULL)
4142 tree_node_old = leveldir_first->node_group;
4146 // get artwork info tree node of first artwork set
4147 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4151 if (tree_dir == NULL)
4152 tree_dir = TREE_USERDIR(type);
4154 if (tree_node_old == NULL ||
4156 tree_subdir_new == NULL) // should not happen
4159 int draw_deactivation_mask = GetDrawDeactivationMask();
4161 // override draw deactivation mask (temporarily disable drawing)
4162 SetDrawDeactivationMask(REDRAW_ALL);
4164 if (type == TREE_TYPE_LEVEL_DIR)
4166 // load new level set config and add it next to first user level set
4167 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4168 tree_node_old->node_parent,
4169 tree_dir, tree_subdir_new);
4173 // load new artwork set config and add it next to first artwork set
4174 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4175 tree_node_old->node_parent,
4176 tree_dir, tree_subdir_new, type);
4179 // set draw deactivation mask to previous value
4180 SetDrawDeactivationMask(draw_deactivation_mask);
4182 // get first node of level or artwork info tree
4183 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4185 // get tree info node of newly added level or artwork set
4186 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4189 if (tree_node_new == NULL) // should not happen
4192 // correct top link and parent node link of newly created tree node
4193 tree_node_new->node_top = tree_node_old->node_top;
4194 tree_node_new->node_parent = tree_node_old->node_parent;
4196 // sort tree info to adjust position of newly added tree set
4197 sortTreeInfo(tree_node_first);
4202 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4203 char *tree_subdir_new, int type)
4205 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4206 Fail("internal tree info structure corrupted -- aborting");
4209 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4211 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4214 char *getArtworkIdentifierForUserLevelSet(int type)
4216 char *classic_artwork_set = getClassicArtworkSet(type);
4218 // check for custom artwork configured in "levelinfo.conf"
4219 char *leveldir_artwork_set =
4220 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4221 boolean has_leveldir_artwork_set =
4222 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4223 classic_artwork_set));
4225 // check for custom artwork in sub-directory "graphics" etc.
4226 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4227 char *leveldir_identifier = leveldir_current->identifier;
4228 boolean has_artwork_subdir =
4229 (getTreeInfoFromIdentifier(artwork_first_node,
4230 leveldir_identifier) != NULL);
4232 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4233 has_artwork_subdir ? leveldir_identifier :
4234 classic_artwork_set);
4237 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4239 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4240 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4241 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4245 ti = getTreeInfoFromIdentifier(artwork_first_node,
4246 ARTWORK_DEFAULT_SUBDIR(type));
4248 Fail("cannot find default graphics -- should not happen");
4254 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4256 char *graphics_set =
4257 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4259 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4261 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4263 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4264 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4265 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4268 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4269 char *level_author, int num_levels)
4271 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4272 char *filename_tmp = getStringCat2(filename, ".tmp");
4274 FILE *file_tmp = NULL;
4275 char line[MAX_LINE_LEN];
4276 boolean success = FALSE;
4277 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4279 // update values in level directory tree
4281 if (level_name != NULL)
4282 setString(&leveldir->name, level_name);
4284 if (level_author != NULL)
4285 setString(&leveldir->author, level_author);
4287 if (num_levels != -1)
4288 leveldir->levels = num_levels;
4290 // update values that depend on other values
4292 setString(&leveldir->name_sorting, leveldir->name);
4294 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4296 // sort order of level sets may have changed
4297 sortTreeInfo(&leveldir_first);
4299 if ((file = fopen(filename, MODE_READ)) &&
4300 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4302 while (fgets(line, MAX_LINE_LEN, file))
4304 if (strPrefix(line, "name:") && level_name != NULL)
4305 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4306 else if (strPrefix(line, "author:") && level_author != NULL)
4307 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4308 else if (strPrefix(line, "levels:") && num_levels != -1)
4309 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4311 fputs(line, file_tmp);
4324 success = (rename(filename_tmp, filename) == 0);
4332 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4333 char *level_author, int num_levels,
4334 boolean use_artwork_set)
4336 LevelDirTree *level_info;
4341 // create user level sub-directory, if needed
4342 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4344 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4346 if (!(file = fopen(filename, MODE_WRITE)))
4348 Warn("cannot write level info file '%s'", filename);
4355 level_info = newTreeInfo();
4357 // always start with reliable default values
4358 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4360 setString(&level_info->name, level_name);
4361 setString(&level_info->author, level_author);
4362 level_info->levels = num_levels;
4363 level_info->first_level = 1;
4364 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4365 level_info->readonly = FALSE;
4367 if (use_artwork_set)
4369 level_info->graphics_set =
4370 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4371 level_info->sounds_set =
4372 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4373 level_info->music_set =
4374 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4377 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4379 fprintFileHeader(file, LEVELINFO_FILENAME);
4382 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4384 if (i == LEVELINFO_TOKEN_NAME ||
4385 i == LEVELINFO_TOKEN_AUTHOR ||
4386 i == LEVELINFO_TOKEN_LEVELS ||
4387 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4388 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4389 i == LEVELINFO_TOKEN_READONLY ||
4390 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4391 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4392 i == LEVELINFO_TOKEN_MUSIC_SET)))
4393 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4395 // just to make things nicer :)
4396 if (i == LEVELINFO_TOKEN_AUTHOR ||
4397 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4398 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4399 fprintf(file, "\n");
4402 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4406 SetFilePermissions(filename, PERMS_PRIVATE);
4408 freeTreeInfo(level_info);
4414 static void SaveUserLevelInfo(void)
4416 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4419 char *getSetupValue(int type, void *value)
4421 static char value_string[MAX_LINE_LEN];
4429 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4433 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4437 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4438 *(int *)value == FALSE ? "off" : "on"));
4442 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4445 case TYPE_YES_NO_AUTO:
4446 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4447 *(int *)value == FALSE ? "no" : "yes"));
4451 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4455 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4459 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4463 sprintf(value_string, "%d", *(int *)value);
4467 if (*(char **)value == NULL)
4470 strcpy(value_string, *(char **)value);
4474 sprintf(value_string, "player_%d", *(int *)value + 1);
4478 value_string[0] = '\0';
4482 if (type & TYPE_GHOSTED)
4483 strcpy(value_string, "n/a");
4485 return value_string;
4488 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4492 static char token_string[MAX_LINE_LEN];
4493 int token_type = token_info[token_nr].type;
4494 void *setup_value = token_info[token_nr].value;
4495 char *token_text = token_info[token_nr].text;
4496 char *value_string = getSetupValue(token_type, setup_value);
4498 // build complete token string
4499 sprintf(token_string, "%s%s", prefix, token_text);
4501 // build setup entry line
4502 line = getFormattedSetupEntry(token_string, value_string);
4504 if (token_type == TYPE_KEY_X11)
4506 Key key = *(Key *)setup_value;
4507 char *keyname = getKeyNameFromKey(key);
4509 // add comment, if useful
4510 if (!strEqual(keyname, "(undefined)") &&
4511 !strEqual(keyname, "(unknown)"))
4513 // add at least one whitespace
4515 for (i = strlen(line); i < token_comment_position; i++)
4519 strcat(line, keyname);
4526 static void InitLastPlayedLevels_ParentNode(void)
4528 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4529 LevelDirTree *leveldir_new = NULL;
4531 // check if parent node for last played levels already exists
4532 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4535 leveldir_new = newTreeInfo();
4537 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4539 leveldir_new->level_group = TRUE;
4541 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4542 setString(&leveldir_new->name, "<< (last played level sets)");
4544 pushTreeInfo(leveldir_top, leveldir_new);
4546 // create node to link back to current level directory
4547 createParentTreeInfoNode(leveldir_new);
4550 void UpdateLastPlayedLevels_TreeInfo(void)
4552 char **last_level_series = setup.level_setup.last_level_series;
4553 boolean reset_leveldir_current = FALSE;
4554 LevelDirTree *leveldir_last;
4555 TreeInfo **node_new = NULL;
4558 if (last_level_series[0] == NULL)
4561 InitLastPlayedLevels_ParentNode();
4563 // check if current level set is from "last played" sub-tree to be rebuilt
4564 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4565 TOKEN_STR_LAST_LEVEL_SERIES);
4567 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4568 TOKEN_STR_LAST_LEVEL_SERIES,
4570 if (leveldir_last == NULL)
4573 node_new = &leveldir_last->node_group->next;
4575 freeTreeInfo(*node_new);
4577 for (i = 0; last_level_series[i] != NULL; i++)
4579 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4580 last_level_series[i]);
4582 *node_new = getTreeInfoCopy(node_last); // copy complete node
4584 (*node_new)->node_top = &leveldir_first; // correct top node link
4585 (*node_new)->node_parent = leveldir_last; // correct parent node link
4587 (*node_new)->node_group = NULL;
4588 (*node_new)->next = NULL;
4590 (*node_new)->cl_first = -1; // force setting tree cursor
4592 node_new = &((*node_new)->next);
4595 if (reset_leveldir_current)
4596 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4597 last_level_series[0]);
4600 static void UpdateLastPlayedLevels_List(void)
4602 char **last_level_series = setup.level_setup.last_level_series;
4603 int pos = MAX_LEVELDIR_HISTORY - 1;
4606 // search for potentially already existing entry in list of level sets
4607 for (i = 0; last_level_series[i] != NULL; i++)
4608 if (strEqual(last_level_series[i], leveldir_current->identifier))
4611 // move list of level sets one entry down (using potentially free entry)
4612 for (i = pos; i > 0; i--)
4613 setString(&last_level_series[i], last_level_series[i - 1]);
4615 // put last played level set at top position
4616 setString(&last_level_series[0], leveldir_current->identifier);
4619 void LoadLevelSetup_LastSeries(void)
4621 // --------------------------------------------------------------------------
4622 // ~/.<program>/levelsetup.conf
4623 // --------------------------------------------------------------------------
4625 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4626 SetupFileHash *level_setup_hash = NULL;
4630 // always start with reliable default values
4631 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4633 // start with empty history of last played level sets
4634 setString(&setup.level_setup.last_level_series[0], NULL);
4636 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4638 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4640 if (leveldir_current == NULL)
4641 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4644 if ((level_setup_hash = loadSetupFileHash(filename)))
4646 char *last_level_series =
4647 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4649 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4651 if (leveldir_current == NULL)
4652 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4654 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4656 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4657 LevelDirTree *leveldir_last;
4659 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4661 last_level_series = getHashEntry(level_setup_hash, token);
4663 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4665 if (leveldir_last != NULL)
4666 setString(&setup.level_setup.last_level_series[pos++],
4670 setString(&setup.level_setup.last_level_series[pos], NULL);
4672 freeSetupFileHash(level_setup_hash);
4676 Debug("setup", "using default setup values");
4682 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4684 // --------------------------------------------------------------------------
4685 // ~/.<program>/levelsetup.conf
4686 // --------------------------------------------------------------------------
4688 // check if the current level directory structure is available at this point
4689 if (leveldir_current == NULL)
4692 char **last_level_series = setup.level_setup.last_level_series;
4693 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4697 InitUserDataDirectory();
4699 UpdateLastPlayedLevels_List();
4701 if (!(file = fopen(filename, MODE_WRITE)))
4703 Warn("cannot write setup file '%s'", filename);
4710 fprintFileHeader(file, LEVELSETUP_FILENAME);
4712 if (deactivate_last_level_series)
4713 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4715 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4716 leveldir_current->identifier));
4718 for (i = 0; last_level_series[i] != NULL; i++)
4720 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4722 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4724 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4729 SetFilePermissions(filename, PERMS_PRIVATE);
4734 void SaveLevelSetup_LastSeries(void)
4736 SaveLevelSetup_LastSeries_Ext(FALSE);
4739 void SaveLevelSetup_LastSeries_Deactivate(void)
4741 SaveLevelSetup_LastSeries_Ext(TRUE);
4744 static void checkSeriesInfo(void)
4746 static char *level_directory = NULL;
4749 DirectoryEntry *dir_entry;
4752 checked_free(level_directory);
4754 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4756 level_directory = getPath2((leveldir_current->in_user_dir ?
4757 getUserLevelDir(NULL) :
4758 options.level_directory),
4759 leveldir_current->fullpath);
4761 if ((dir = openDirectory(level_directory)) == NULL)
4763 Warn("cannot read level directory '%s'", level_directory);
4769 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4771 if (strlen(dir_entry->basename) > 4 &&
4772 dir_entry->basename[3] == '.' &&
4773 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4775 char levelnum_str[4];
4778 strncpy(levelnum_str, dir_entry->basename, 3);
4779 levelnum_str[3] = '\0';
4781 levelnum_value = atoi(levelnum_str);
4783 if (levelnum_value < leveldir_current->first_level)
4785 Warn("additional level %d found", levelnum_value);
4787 leveldir_current->first_level = levelnum_value;
4789 else if (levelnum_value > leveldir_current->last_level)
4791 Warn("additional level %d found", levelnum_value);
4793 leveldir_current->last_level = levelnum_value;
4799 closeDirectory(dir);
4802 void LoadLevelSetup_SeriesInfo(void)
4805 SetupFileHash *level_setup_hash = NULL;
4806 char *level_subdir = leveldir_current->subdir;
4809 // always start with reliable default values
4810 level_nr = leveldir_current->first_level;
4812 for (i = 0; i < MAX_LEVELS; i++)
4814 LevelStats_setPlayed(i, 0);
4815 LevelStats_setSolved(i, 0);
4820 // --------------------------------------------------------------------------
4821 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4822 // --------------------------------------------------------------------------
4824 level_subdir = leveldir_current->subdir;
4826 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4828 if ((level_setup_hash = loadSetupFileHash(filename)))
4832 // get last played level in this level set
4834 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4838 level_nr = atoi(token_value);
4840 if (level_nr < leveldir_current->first_level)
4841 level_nr = leveldir_current->first_level;
4842 if (level_nr > leveldir_current->last_level)
4843 level_nr = leveldir_current->last_level;
4846 // get handicap level in this level set
4848 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4852 int level_nr = atoi(token_value);
4854 if (level_nr < leveldir_current->first_level)
4855 level_nr = leveldir_current->first_level;
4856 if (level_nr > leveldir_current->last_level + 1)
4857 level_nr = leveldir_current->last_level;
4859 if (leveldir_current->user_defined || !leveldir_current->handicap)
4860 level_nr = leveldir_current->last_level;
4862 leveldir_current->handicap_level = level_nr;
4865 // get number of played and solved levels in this level set
4867 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4869 char *token = HASH_ITERATION_TOKEN(itr);
4870 char *value = HASH_ITERATION_VALUE(itr);
4872 if (strlen(token) == 3 &&
4873 token[0] >= '0' && token[0] <= '9' &&
4874 token[1] >= '0' && token[1] <= '9' &&
4875 token[2] >= '0' && token[2] <= '9')
4877 int level_nr = atoi(token);
4880 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4882 value = strchr(value, ' ');
4885 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4888 END_HASH_ITERATION(hash, itr)
4890 freeSetupFileHash(level_setup_hash);
4894 Debug("setup", "using default setup values");
4900 void SaveLevelSetup_SeriesInfo(void)
4903 char *level_subdir = leveldir_current->subdir;
4904 char *level_nr_str = int2str(level_nr, 0);
4905 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4909 // --------------------------------------------------------------------------
4910 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4911 // --------------------------------------------------------------------------
4913 InitLevelSetupDirectory(level_subdir);
4915 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4917 if (!(file = fopen(filename, MODE_WRITE)))
4919 Warn("cannot write setup file '%s'", filename);
4926 fprintFileHeader(file, LEVELSETUP_FILENAME);
4928 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4930 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4931 handicap_level_str));
4933 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4936 if (LevelStats_getPlayed(i) > 0 ||
4937 LevelStats_getSolved(i) > 0)
4942 sprintf(token, "%03d", i);
4943 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4945 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4951 SetFilePermissions(filename, PERMS_PRIVATE);
4956 int LevelStats_getPlayed(int nr)
4958 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4961 int LevelStats_getSolved(int nr)
4963 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4966 void LevelStats_setPlayed(int nr, int value)
4968 if (nr >= 0 && nr < MAX_LEVELS)
4969 level_stats[nr].played = value;
4972 void LevelStats_setSolved(int nr, int value)
4974 if (nr >= 0 && nr < MAX_LEVELS)
4975 level_stats[nr].solved = value;
4978 void LevelStats_incPlayed(int nr)
4980 if (nr >= 0 && nr < MAX_LEVELS)
4981 level_stats[nr].played++;
4984 void LevelStats_incSolved(int nr)
4986 if (nr >= 0 && nr < MAX_LEVELS)
4987 level_stats[nr].solved++;
4990 void LoadUserSetup(void)
4992 // --------------------------------------------------------------------------
4993 // ~/.<program>/usersetup.conf
4994 // --------------------------------------------------------------------------
4996 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4997 SetupFileHash *user_setup_hash = NULL;
4999 // always start with reliable default values
5002 if ((user_setup_hash = loadSetupFileHash(filename)))
5006 // get last selected user number
5007 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5010 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5012 freeSetupFileHash(user_setup_hash);
5016 Debug("setup", "using default setup values");
5022 void SaveUserSetup(void)
5024 // --------------------------------------------------------------------------
5025 // ~/.<program>/usersetup.conf
5026 // --------------------------------------------------------------------------
5028 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5031 InitMainUserDataDirectory();
5033 if (!(file = fopen(filename, MODE_WRITE)))
5035 Warn("cannot write setup file '%s'", filename);
5042 fprintFileHeader(file, USERSETUP_FILENAME);
5044 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5048 SetFilePermissions(filename, PERMS_PRIVATE);