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 class_sorting1 = 0, class_sorting2 = 0;
2864 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2866 class_sorting1 = LEVELSORTING(entry1);
2867 class_sorting2 = LEVELSORTING(entry2);
2869 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2870 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2871 entry1->type == TREE_TYPE_MUSIC_DIR)
2873 class_sorting1 = ARTWORKSORTING(entry1);
2874 class_sorting2 = ARTWORKSORTING(entry2);
2877 if (entry1->parent_link || entry2->parent_link)
2878 compare_result = (entry1->parent_link ? -1 : +1);
2879 else if (entry1->level_group != entry2->level_group)
2880 compare_result = (entry1->level_group ? -1 : +1);
2881 else if (entry1->sort_priority == entry2->sort_priority)
2882 compare_result = strcasecmp(entry1->name_sorting, entry2->name_sorting);
2883 else if (class_sorting1 == class_sorting2)
2884 compare_result = entry1->sort_priority - entry2->sort_priority;
2886 compare_result = class_sorting1 - class_sorting2;
2888 return compare_result;
2891 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2895 if (node_parent == NULL)
2898 ti_new = newTreeInfo();
2899 setTreeInfoToDefaults(ti_new, node_parent->type);
2901 ti_new->node_parent = node_parent;
2902 ti_new->parent_link = TRUE;
2904 setString(&ti_new->identifier, node_parent->identifier);
2905 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2906 setString(&ti_new->name_sorting, ti_new->name);
2908 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2909 setString(&ti_new->fullpath, node_parent->fullpath);
2911 ti_new->sort_priority = node_parent->sort_priority;
2912 ti_new->latest_engine = node_parent->latest_engine;
2914 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2916 pushTreeInfo(&node_parent->node_group, ti_new);
2921 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2923 if (node_first == NULL)
2926 TreeInfo *ti_new = newTreeInfo();
2927 int type = node_first->type;
2929 setTreeInfoToDefaults(ti_new, type);
2931 ti_new->node_parent = NULL;
2932 ti_new->parent_link = FALSE;
2934 setString(&ti_new->identifier, node_first->identifier);
2935 setString(&ti_new->name, TREE_INFOTEXT(type));
2936 setString(&ti_new->name_sorting, ti_new->name);
2938 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2939 setString(&ti_new->fullpath, ".");
2941 ti_new->sort_priority = node_first->sort_priority;;
2942 ti_new->latest_engine = node_first->latest_engine;
2944 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2946 ti_new->node_group = node_first;
2947 ti_new->level_group = TRUE;
2949 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2951 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2952 setString(&ti_new2->name_sorting, ti_new2->name);
2957 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2961 if (node->node_group)
2962 setTreeInfoParentNodes(node->node_group, node);
2964 node->node_parent = node_parent;
2971 // ----------------------------------------------------------------------------
2972 // functions for handling level and custom artwork info cache
2973 // ----------------------------------------------------------------------------
2975 static void LoadArtworkInfoCache(void)
2977 InitCacheDirectory();
2979 if (artworkinfo_cache_old == NULL)
2981 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2983 // try to load artwork info hash from already existing cache file
2984 artworkinfo_cache_old = loadSetupFileHash(filename);
2986 // if no artwork info cache file was found, start with empty hash
2987 if (artworkinfo_cache_old == NULL)
2988 artworkinfo_cache_old = newSetupFileHash();
2993 if (artworkinfo_cache_new == NULL)
2994 artworkinfo_cache_new = newSetupFileHash();
2996 update_artworkinfo_cache = FALSE;
2999 static void SaveArtworkInfoCache(void)
3001 if (!update_artworkinfo_cache)
3004 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3006 InitCacheDirectory();
3008 saveSetupFileHash(artworkinfo_cache_new, filename);
3013 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3015 static char *prefix = NULL;
3017 checked_free(prefix);
3019 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3024 // (identical to above function, but separate string buffer needed -- nasty)
3025 static char *getCacheToken(char *prefix, char *suffix)
3027 static char *token = NULL;
3029 checked_free(token);
3031 token = getStringCat2WithSeparator(prefix, suffix, ".");
3036 static char *getFileTimestampString(char *filename)
3038 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3041 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3043 struct stat file_status;
3045 if (timestamp_string == NULL)
3048 if (!fileExists(filename)) // file does not exist
3049 return (atoi(timestamp_string) != 0);
3051 if (stat(filename, &file_status) != 0) // cannot stat file
3054 return (file_status.st_mtime != atoi(timestamp_string));
3057 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3059 char *identifier = level_node->subdir;
3060 char *type_string = ARTWORK_DIRECTORY(type);
3061 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3062 char *token_main = getCacheToken(token_prefix, "CACHED");
3063 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3064 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3065 TreeInfo *artwork_info = NULL;
3067 if (!use_artworkinfo_cache)
3070 if (optional_tokens_hash == NULL)
3074 // create hash from list of optional tokens (for quick access)
3075 optional_tokens_hash = newSetupFileHash();
3076 for (i = 0; optional_tokens[i] != NULL; i++)
3077 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3084 artwork_info = newTreeInfo();
3085 setTreeInfoToDefaults(artwork_info, type);
3087 // set all structure fields according to the token/value pairs
3088 ldi = *artwork_info;
3089 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3091 char *token_suffix = artworkinfo_tokens[i].text;
3092 char *token = getCacheToken(token_prefix, token_suffix);
3093 char *value = getHashEntry(artworkinfo_cache_old, token);
3095 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3097 setSetupInfo(artworkinfo_tokens, i, value);
3099 // check if cache entry for this item is mandatory, but missing
3100 if (value == NULL && !optional)
3102 Warn("missing cache entry '%s'", token);
3108 *artwork_info = ldi;
3113 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3114 LEVELINFO_FILENAME);
3115 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3116 ARTWORKINFO_FILENAME(type));
3118 // check if corresponding "levelinfo.conf" file has changed
3119 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3120 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3122 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3125 // check if corresponding "<artworkinfo>.conf" file has changed
3126 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3127 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3129 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3132 checked_free(filename_levelinfo);
3133 checked_free(filename_artworkinfo);
3136 if (!cached && artwork_info != NULL)
3138 freeTreeInfo(artwork_info);
3143 return artwork_info;
3146 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3147 LevelDirTree *level_node, int type)
3149 char *identifier = level_node->subdir;
3150 char *type_string = ARTWORK_DIRECTORY(type);
3151 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3152 char *token_main = getCacheToken(token_prefix, "CACHED");
3153 boolean set_cache_timestamps = TRUE;
3156 setHashEntry(artworkinfo_cache_new, token_main, "true");
3158 if (set_cache_timestamps)
3160 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3161 LEVELINFO_FILENAME);
3162 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3163 ARTWORKINFO_FILENAME(type));
3164 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3165 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3167 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3168 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3170 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3171 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3173 checked_free(filename_levelinfo);
3174 checked_free(filename_artworkinfo);
3175 checked_free(timestamp_levelinfo);
3176 checked_free(timestamp_artworkinfo);
3179 ldi = *artwork_info;
3180 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3182 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3183 char *value = getSetupValue(artworkinfo_tokens[i].type,
3184 artworkinfo_tokens[i].value);
3186 setHashEntry(artworkinfo_cache_new, token, value);
3191 // ----------------------------------------------------------------------------
3192 // functions for loading level info and custom artwork info
3193 // ----------------------------------------------------------------------------
3195 int GetZipFileTreeType(char *zip_filename)
3197 static char *top_dir_path = NULL;
3198 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3199 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3201 GRAPHICSINFO_FILENAME,
3202 SOUNDSINFO_FILENAME,
3208 checked_free(top_dir_path);
3209 top_dir_path = NULL;
3211 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3213 checked_free(top_dir_conf_filename[j]);
3214 top_dir_conf_filename[j] = NULL;
3217 char **zip_entries = zip_list(zip_filename);
3219 // check if zip file successfully opened
3220 if (zip_entries == NULL || zip_entries[0] == NULL)
3221 return TREE_TYPE_UNDEFINED;
3223 // first zip file entry is expected to be top level directory
3224 char *top_dir = zip_entries[0];
3226 // check if valid top level directory found in zip file
3227 if (!strSuffix(top_dir, "/"))
3228 return TREE_TYPE_UNDEFINED;
3230 // get filenames of valid configuration files in top level directory
3231 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3232 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3234 int tree_type = TREE_TYPE_UNDEFINED;
3237 while (zip_entries[e] != NULL)
3239 // check if every zip file entry is below top level directory
3240 if (!strPrefix(zip_entries[e], top_dir))
3241 return TREE_TYPE_UNDEFINED;
3243 // check if this zip file entry is a valid configuration filename
3244 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3246 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3248 // only exactly one valid configuration file allowed
3249 if (tree_type != TREE_TYPE_UNDEFINED)
3250 return TREE_TYPE_UNDEFINED;
3262 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3265 static char *top_dir_path = NULL;
3266 static char *top_dir_conf_filename = NULL;
3268 checked_free(top_dir_path);
3269 checked_free(top_dir_conf_filename);
3271 top_dir_path = NULL;
3272 top_dir_conf_filename = NULL;
3274 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3275 ARTWORKINFO_FILENAME(tree_type));
3277 // check if valid configuration filename determined
3278 if (conf_basename == NULL || strEqual(conf_basename, ""))
3281 char **zip_entries = zip_list(zip_filename);
3283 // check if zip file successfully opened
3284 if (zip_entries == NULL || zip_entries[0] == NULL)
3287 // first zip file entry is expected to be top level directory
3288 char *top_dir = zip_entries[0];
3290 // check if valid top level directory found in zip file
3291 if (!strSuffix(top_dir, "/"))
3294 // get path of extracted top level directory
3295 top_dir_path = getPath2(directory, top_dir);
3297 // remove trailing directory separator from top level directory path
3298 // (required to be able to check for file and directory in next step)
3299 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3301 // check if zip file's top level directory already exists in target directory
3302 if (fileExists(top_dir_path)) // (checks for file and directory)
3305 // get filename of configuration file in top level directory
3306 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3308 boolean found_top_dir_conf_filename = FALSE;
3311 while (zip_entries[i] != NULL)
3313 // check if every zip file entry is below top level directory
3314 if (!strPrefix(zip_entries[i], top_dir))
3317 // check if this zip file entry is the configuration filename
3318 if (strEqual(zip_entries[i], top_dir_conf_filename))
3319 found_top_dir_conf_filename = TRUE;
3324 // check if valid configuration filename was found in zip file
3325 if (!found_top_dir_conf_filename)
3331 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3334 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3337 if (!zip_file_valid)
3339 Warn("zip file '%s' rejected!", zip_filename);
3344 char **zip_entries = zip_extract(zip_filename, directory);
3346 if (zip_entries == NULL)
3348 Warn("zip file '%s' could not be extracted!", zip_filename);
3353 Info("zip file '%s' successfully extracted!", zip_filename);
3355 // first zip file entry contains top level directory
3356 char *top_dir = zip_entries[0];
3358 // remove trailing directory separator from top level directory
3359 top_dir[strlen(top_dir) - 1] = '\0';
3364 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3367 DirectoryEntry *dir_entry;
3369 if ((dir = openDirectory(directory)) == NULL)
3371 // display error if directory is main "options.graphics_directory" etc.
3372 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3373 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3374 Warn("cannot read directory '%s'", directory);
3379 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3381 // skip non-zip files (and also directories with zip extension)
3382 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3385 char *zip_filename = getPath2(directory, dir_entry->basename);
3386 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3387 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3389 // check if zip file hasn't already been extracted or rejected
3390 if (!fileExists(zip_filename_extracted) &&
3391 !fileExists(zip_filename_rejected))
3393 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3395 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3396 zip_filename_rejected);
3399 // create empty file to mark zip file as extracted or rejected
3400 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3401 fclose(marker_file);
3404 free(zip_filename_extracted);
3405 free(zip_filename_rejected);
3409 closeDirectory(dir);
3412 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3413 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3415 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3416 TreeInfo *node_parent,
3417 char *level_directory,
3418 char *directory_name)
3420 char *directory_path = getPath2(level_directory, directory_name);
3421 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3422 SetupFileHash *setup_file_hash;
3423 LevelDirTree *leveldir_new = NULL;
3426 // unless debugging, silently ignore directories without "levelinfo.conf"
3427 if (!options.debug && !fileExists(filename))
3429 free(directory_path);
3435 setup_file_hash = loadSetupFileHash(filename);
3437 if (setup_file_hash == NULL)
3439 #if DEBUG_NO_CONFIG_FILE
3440 Debug("setup", "ignoring level directory '%s'", directory_path);
3443 free(directory_path);
3449 leveldir_new = newTreeInfo();
3452 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3454 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3456 leveldir_new->subdir = getStringCopy(directory_name);
3458 // set all structure fields according to the token/value pairs
3459 ldi = *leveldir_new;
3460 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3461 setSetupInfo(levelinfo_tokens, i,
3462 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3463 *leveldir_new = ldi;
3465 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3466 setString(&leveldir_new->name, leveldir_new->subdir);
3468 if (leveldir_new->identifier == NULL)
3469 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3471 if (leveldir_new->name_sorting == NULL)
3472 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3474 if (node_parent == NULL) // top level group
3476 leveldir_new->basepath = getStringCopy(level_directory);
3477 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3479 else // sub level group
3481 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3482 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3485 leveldir_new->last_level =
3486 leveldir_new->first_level + leveldir_new->levels - 1;
3488 leveldir_new->in_user_dir =
3489 (!strEqual(leveldir_new->basepath, options.level_directory));
3491 // adjust some settings if user's private level directory was detected
3492 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3493 leveldir_new->in_user_dir &&
3494 (strEqual(leveldir_new->subdir, getLoginName()) ||
3495 strEqual(leveldir_new->name, getLoginName()) ||
3496 strEqual(leveldir_new->author, getRealName())))
3498 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3499 leveldir_new->readonly = FALSE;
3502 leveldir_new->user_defined =
3503 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3505 leveldir_new->color = LEVELCOLOR(leveldir_new);
3507 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3509 leveldir_new->handicap_level = // set handicap to default value
3510 (leveldir_new->user_defined || !leveldir_new->handicap ?
3511 leveldir_new->last_level : leveldir_new->first_level);
3513 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3515 pushTreeInfo(node_first, leveldir_new);
3517 freeSetupFileHash(setup_file_hash);
3519 if (leveldir_new->level_group)
3521 // create node to link back to current level directory
3522 createParentTreeInfoNode(leveldir_new);
3524 // recursively step into sub-directory and look for more level series
3525 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3526 leveldir_new, directory_path);
3529 free(directory_path);
3535 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3536 TreeInfo *node_parent,
3537 char *level_directory)
3539 // ---------- 1st stage: process any level set zip files ----------
3541 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3543 // ---------- 2nd stage: check for level set directories ----------
3546 DirectoryEntry *dir_entry;
3547 boolean valid_entry_found = FALSE;
3549 if ((dir = openDirectory(level_directory)) == NULL)
3551 Warn("cannot read level directory '%s'", level_directory);
3556 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3558 char *directory_name = dir_entry->basename;
3559 char *directory_path = getPath2(level_directory, directory_name);
3561 // skip entries for current and parent directory
3562 if (strEqual(directory_name, ".") ||
3563 strEqual(directory_name, ".."))
3565 free(directory_path);
3570 // find out if directory entry is itself a directory
3571 if (!dir_entry->is_directory) // not a directory
3573 free(directory_path);
3578 free(directory_path);
3580 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3581 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3582 strEqual(directory_name, MUSIC_DIRECTORY))
3585 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3590 closeDirectory(dir);
3592 // special case: top level directory may directly contain "levelinfo.conf"
3593 if (node_parent == NULL && !valid_entry_found)
3595 // check if this directory directly contains a file "levelinfo.conf"
3596 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3597 level_directory, ".");
3600 if (!valid_entry_found)
3601 Warn("cannot find any valid level series in directory '%s'",
3605 boolean AdjustGraphicsForEMC(void)
3607 boolean settings_changed = FALSE;
3609 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3610 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3612 return settings_changed;
3615 boolean AdjustSoundsForEMC(void)
3617 boolean settings_changed = FALSE;
3619 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3620 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3622 return settings_changed;
3625 void LoadLevelInfo(void)
3627 InitUserLevelDirectory(getLoginName());
3629 DrawInitText("Loading level series", 120, FC_GREEN);
3631 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3632 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3634 leveldir_first = createTopTreeInfoNode(leveldir_first);
3636 /* after loading all level set information, clone the level directory tree
3637 and remove all level sets without levels (these may still contain artwork
3638 to be offered in the setup menu as "custom artwork", and are therefore
3639 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3640 leveldir_first_all = leveldir_first;
3641 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3643 AdjustGraphicsForEMC();
3644 AdjustSoundsForEMC();
3646 // before sorting, the first entries will be from the user directory
3647 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3649 if (leveldir_first == NULL)
3650 Fail("cannot find any valid level series in any directory");
3652 sortTreeInfo(&leveldir_first);
3654 #if ENABLE_UNUSED_CODE
3655 dumpTreeInfo(leveldir_first, 0);
3659 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3660 TreeInfo *node_parent,
3661 char *base_directory,
3662 char *directory_name, int type)
3664 char *directory_path = getPath2(base_directory, directory_name);
3665 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3666 SetupFileHash *setup_file_hash = NULL;
3667 TreeInfo *artwork_new = NULL;
3670 if (fileExists(filename))
3671 setup_file_hash = loadSetupFileHash(filename);
3673 if (setup_file_hash == NULL) // no config file -- look for artwork files
3676 DirectoryEntry *dir_entry;
3677 boolean valid_file_found = FALSE;
3679 if ((dir = openDirectory(directory_path)) != NULL)
3681 while ((dir_entry = readDirectory(dir)) != NULL)
3683 if (FileIsArtworkType(dir_entry->filename, type))
3685 valid_file_found = TRUE;
3691 closeDirectory(dir);
3694 if (!valid_file_found)
3696 #if DEBUG_NO_CONFIG_FILE
3697 if (!strEqual(directory_name, "."))
3698 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3701 free(directory_path);
3708 artwork_new = newTreeInfo();
3711 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3713 setTreeInfoToDefaults(artwork_new, type);
3715 artwork_new->subdir = getStringCopy(directory_name);
3717 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3719 // set all structure fields according to the token/value pairs
3721 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3722 setSetupInfo(levelinfo_tokens, i,
3723 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3726 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3727 setString(&artwork_new->name, artwork_new->subdir);
3729 if (artwork_new->identifier == NULL)
3730 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3732 if (artwork_new->name_sorting == NULL)
3733 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3736 if (node_parent == NULL) // top level group
3738 artwork_new->basepath = getStringCopy(base_directory);
3739 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3741 else // sub level group
3743 artwork_new->basepath = getStringCopy(node_parent->basepath);
3744 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3747 artwork_new->in_user_dir =
3748 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3750 // (may use ".sort_priority" from "setup_file_hash" above)
3751 artwork_new->color = ARTWORKCOLOR(artwork_new);
3753 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3755 if (setup_file_hash == NULL) // (after determining ".user_defined")
3757 if (strEqual(artwork_new->subdir, "."))
3759 if (artwork_new->user_defined)
3761 setString(&artwork_new->identifier, "private");
3762 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3766 setString(&artwork_new->identifier, "classic");
3767 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3770 // set to new values after changing ".sort_priority"
3771 artwork_new->color = ARTWORKCOLOR(artwork_new);
3773 setString(&artwork_new->class_desc,
3774 getLevelClassDescription(artwork_new));
3778 setString(&artwork_new->identifier, artwork_new->subdir);
3781 setString(&artwork_new->name, artwork_new->identifier);
3782 setString(&artwork_new->name_sorting, artwork_new->name);
3785 pushTreeInfo(node_first, artwork_new);
3787 freeSetupFileHash(setup_file_hash);
3789 free(directory_path);
3795 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3796 TreeInfo *node_parent,
3797 char *base_directory, int type)
3799 // ---------- 1st stage: process any artwork set zip files ----------
3801 ProcessZipFilesInDirectory(base_directory, type);
3803 // ---------- 2nd stage: check for artwork set directories ----------
3806 DirectoryEntry *dir_entry;
3807 boolean valid_entry_found = FALSE;
3809 if ((dir = openDirectory(base_directory)) == NULL)
3811 // display error if directory is main "options.graphics_directory" etc.
3812 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3813 Warn("cannot read directory '%s'", base_directory);
3818 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3820 char *directory_name = dir_entry->basename;
3821 char *directory_path = getPath2(base_directory, directory_name);
3823 // skip directory entries for current and parent directory
3824 if (strEqual(directory_name, ".") ||
3825 strEqual(directory_name, ".."))
3827 free(directory_path);
3832 // skip directory entries which are not a directory
3833 if (!dir_entry->is_directory) // not a directory
3835 free(directory_path);
3840 free(directory_path);
3842 // check if this directory contains artwork with or without config file
3843 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3845 directory_name, type);
3848 closeDirectory(dir);
3850 // check if this directory directly contains artwork itself
3851 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3852 base_directory, ".",
3854 if (!valid_entry_found)
3855 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3858 static TreeInfo *getDummyArtworkInfo(int type)
3860 // this is only needed when there is completely no artwork available
3861 TreeInfo *artwork_new = newTreeInfo();
3863 setTreeInfoToDefaults(artwork_new, type);
3865 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3866 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3867 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3869 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3870 setString(&artwork_new->name, UNDEFINED_FILENAME);
3871 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3876 void SetCurrentArtwork(int type)
3878 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3879 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3880 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3881 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3883 // set current artwork to artwork configured in setup menu
3884 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3886 // if not found, set current artwork to default artwork
3887 if (*current_ptr == NULL)
3888 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3890 // if not found, set current artwork to first artwork in tree
3891 if (*current_ptr == NULL)
3892 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3895 void ChangeCurrentArtworkIfNeeded(int type)
3897 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3898 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3900 if (!strEqual(current_identifier, setup_set))
3901 SetCurrentArtwork(type);
3904 void LoadArtworkInfo(void)
3906 LoadArtworkInfoCache();
3908 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3910 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3911 options.graphics_directory,
3912 TREE_TYPE_GRAPHICS_DIR);
3913 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3914 getUserGraphicsDir(),
3915 TREE_TYPE_GRAPHICS_DIR);
3917 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3918 options.sounds_directory,
3919 TREE_TYPE_SOUNDS_DIR);
3920 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3922 TREE_TYPE_SOUNDS_DIR);
3924 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3925 options.music_directory,
3926 TREE_TYPE_MUSIC_DIR);
3927 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3929 TREE_TYPE_MUSIC_DIR);
3931 if (artwork.gfx_first == NULL)
3932 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3933 if (artwork.snd_first == NULL)
3934 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3935 if (artwork.mus_first == NULL)
3936 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3938 // before sorting, the first entries will be from the user directory
3939 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3940 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3941 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3943 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3944 artwork.snd_current_identifier = artwork.snd_current->identifier;
3945 artwork.mus_current_identifier = artwork.mus_current->identifier;
3947 #if ENABLE_UNUSED_CODE
3948 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3949 artwork.gfx_current_identifier);
3950 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3951 artwork.snd_current_identifier);
3952 Debug("setup:LoadArtworkInfo", "music set == %s",
3953 artwork.mus_current_identifier);
3956 sortTreeInfo(&artwork.gfx_first);
3957 sortTreeInfo(&artwork.snd_first);
3958 sortTreeInfo(&artwork.mus_first);
3960 #if ENABLE_UNUSED_CODE
3961 dumpTreeInfo(artwork.gfx_first, 0);
3962 dumpTreeInfo(artwork.snd_first, 0);
3963 dumpTreeInfo(artwork.mus_first, 0);
3967 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3969 ArtworkDirTree *artwork_new = newTreeInfo();
3970 char *top_node_name = "standalone artwork";
3972 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3974 artwork_new->level_group = TRUE;
3976 setString(&artwork_new->identifier, top_node_name);
3977 setString(&artwork_new->name, top_node_name);
3978 setString(&artwork_new->name_sorting, top_node_name);
3980 // create node to link back to current custom artwork directory
3981 createParentTreeInfoNode(artwork_new);
3983 // move existing custom artwork tree into newly created sub-tree
3984 artwork_new->node_group->next = *artwork_node;
3986 // change custom artwork tree to contain only newly created node
3987 *artwork_node = artwork_new;
3990 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3991 ArtworkDirTree *node_parent,
3992 LevelDirTree *level_node,
3993 boolean empty_level_set_mode)
3995 int type = (*artwork_node)->type;
3997 // recursively check all level directories for artwork sub-directories
4001 boolean empty_level_set = (level_node->levels == 0);
4003 // check all tree entries for artwork, but skip parent link entries
4004 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4006 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4007 boolean cached = (artwork_new != NULL);
4011 pushTreeInfo(artwork_node, artwork_new);
4015 TreeInfo *topnode_last = *artwork_node;
4016 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4017 ARTWORK_DIRECTORY(type));
4019 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4021 if (topnode_last != *artwork_node) // check for newly added node
4023 artwork_new = *artwork_node;
4025 setString(&artwork_new->identifier, level_node->subdir);
4026 setString(&artwork_new->name, level_node->name);
4027 setString(&artwork_new->name_sorting, level_node->name_sorting);
4029 artwork_new->sort_priority = level_node->sort_priority;
4030 artwork_new->in_user_dir = level_node->in_user_dir;
4031 artwork_new->color = LEVELCOLOR(artwork_new);
4033 update_artworkinfo_cache = TRUE;
4039 // insert artwork info (from old cache or filesystem) into new cache
4040 if (artwork_new != NULL)
4041 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4044 DrawInitText(level_node->name, 150, FC_YELLOW);
4046 if (level_node->node_group != NULL)
4048 TreeInfo *artwork_new = newTreeInfo();
4051 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4053 setTreeInfoToDefaults(artwork_new, type);
4055 artwork_new->level_group = TRUE;
4057 setString(&artwork_new->identifier, level_node->subdir);
4059 if (node_parent == NULL) // check for top tree node
4061 char *top_node_name = (empty_level_set_mode ?
4062 "artwork for certain level sets" :
4063 "artwork included in level sets");
4065 setString(&artwork_new->name, top_node_name);
4066 setString(&artwork_new->name_sorting, top_node_name);
4070 setString(&artwork_new->name, level_node->name);
4071 setString(&artwork_new->name_sorting, level_node->name_sorting);
4074 pushTreeInfo(artwork_node, artwork_new);
4076 // create node to link back to current custom artwork directory
4077 createParentTreeInfoNode(artwork_new);
4079 // recursively step into sub-directory and look for more custom artwork
4080 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4081 level_node->node_group,
4082 empty_level_set_mode);
4084 // if sub-tree has no custom artwork at all, remove it
4085 if (artwork_new->node_group->next == NULL)
4086 removeTreeInfo(artwork_node);
4089 level_node = level_node->next;
4093 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4095 // move peviously loaded artwork tree into separate sub-tree
4096 MoveArtworkInfoIntoSubTree(artwork_node);
4098 // load artwork from level sets into separate sub-trees
4099 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4100 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4102 // add top tree node over all three separate sub-trees
4103 *artwork_node = createTopTreeInfoNode(*artwork_node);
4105 // set all parent links (back links) in complete artwork tree
4106 setTreeInfoParentNodes(*artwork_node, NULL);
4109 void LoadLevelArtworkInfo(void)
4111 print_timestamp_init("LoadLevelArtworkInfo");
4113 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4115 print_timestamp_time("DrawTimeText");
4117 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4118 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4119 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4120 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4121 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4122 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4124 SaveArtworkInfoCache();
4126 print_timestamp_time("SaveArtworkInfoCache");
4128 // needed for reloading level artwork not known at ealier stage
4129 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4130 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4131 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4133 print_timestamp_time("getTreeInfoFromIdentifier");
4135 sortTreeInfo(&artwork.gfx_first);
4136 sortTreeInfo(&artwork.snd_first);
4137 sortTreeInfo(&artwork.mus_first);
4139 print_timestamp_time("sortTreeInfo");
4141 #if ENABLE_UNUSED_CODE
4142 dumpTreeInfo(artwork.gfx_first, 0);
4143 dumpTreeInfo(artwork.snd_first, 0);
4144 dumpTreeInfo(artwork.mus_first, 0);
4147 print_timestamp_done("LoadLevelArtworkInfo");
4150 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4151 char *tree_subdir_new, int type)
4153 if (tree_node_old == NULL)
4155 if (type == TREE_TYPE_LEVEL_DIR)
4157 // get level info tree node of personal user level set
4158 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4160 // this may happen if "setup.internal.create_user_levelset" is FALSE
4161 // or if file "levelinfo.conf" is missing in personal user level set
4162 if (tree_node_old == NULL)
4163 tree_node_old = leveldir_first->node_group;
4167 // get artwork info tree node of first artwork set
4168 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4172 if (tree_dir == NULL)
4173 tree_dir = TREE_USERDIR(type);
4175 if (tree_node_old == NULL ||
4177 tree_subdir_new == NULL) // should not happen
4180 int draw_deactivation_mask = GetDrawDeactivationMask();
4182 // override draw deactivation mask (temporarily disable drawing)
4183 SetDrawDeactivationMask(REDRAW_ALL);
4185 if (type == TREE_TYPE_LEVEL_DIR)
4187 // load new level set config and add it next to first user level set
4188 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4189 tree_node_old->node_parent,
4190 tree_dir, tree_subdir_new);
4194 // load new artwork set config and add it next to first artwork set
4195 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4196 tree_node_old->node_parent,
4197 tree_dir, tree_subdir_new, type);
4200 // set draw deactivation mask to previous value
4201 SetDrawDeactivationMask(draw_deactivation_mask);
4203 // get first node of level or artwork info tree
4204 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4206 // get tree info node of newly added level or artwork set
4207 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4210 if (tree_node_new == NULL) // should not happen
4213 // correct top link and parent node link of newly created tree node
4214 tree_node_new->node_top = tree_node_old->node_top;
4215 tree_node_new->node_parent = tree_node_old->node_parent;
4217 // sort tree info to adjust position of newly added tree set
4218 sortTreeInfo(tree_node_first);
4223 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4224 char *tree_subdir_new, int type)
4226 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4227 Fail("internal tree info structure corrupted -- aborting");
4230 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4232 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4235 char *getArtworkIdentifierForUserLevelSet(int type)
4237 char *classic_artwork_set = getClassicArtworkSet(type);
4239 // check for custom artwork configured in "levelinfo.conf"
4240 char *leveldir_artwork_set =
4241 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4242 boolean has_leveldir_artwork_set =
4243 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4244 classic_artwork_set));
4246 // check for custom artwork in sub-directory "graphics" etc.
4247 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4248 char *leveldir_identifier = leveldir_current->identifier;
4249 boolean has_artwork_subdir =
4250 (getTreeInfoFromIdentifier(artwork_first_node,
4251 leveldir_identifier) != NULL);
4253 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4254 has_artwork_subdir ? leveldir_identifier :
4255 classic_artwork_set);
4258 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4260 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4261 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4262 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4266 ti = getTreeInfoFromIdentifier(artwork_first_node,
4267 ARTWORK_DEFAULT_SUBDIR(type));
4269 Fail("cannot find default graphics -- should not happen");
4275 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4277 char *graphics_set =
4278 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4280 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4282 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4284 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4285 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4286 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4289 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4290 char *level_author, int num_levels)
4292 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4293 char *filename_tmp = getStringCat2(filename, ".tmp");
4295 FILE *file_tmp = NULL;
4296 char line[MAX_LINE_LEN];
4297 boolean success = FALSE;
4298 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4300 // update values in level directory tree
4302 if (level_name != NULL)
4303 setString(&leveldir->name, level_name);
4305 if (level_author != NULL)
4306 setString(&leveldir->author, level_author);
4308 if (num_levels != -1)
4309 leveldir->levels = num_levels;
4311 // update values that depend on other values
4313 setString(&leveldir->name_sorting, leveldir->name);
4315 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4317 // sort order of level sets may have changed
4318 sortTreeInfo(&leveldir_first);
4320 if ((file = fopen(filename, MODE_READ)) &&
4321 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4323 while (fgets(line, MAX_LINE_LEN, file))
4325 if (strPrefix(line, "name:") && level_name != NULL)
4326 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4327 else if (strPrefix(line, "author:") && level_author != NULL)
4328 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4329 else if (strPrefix(line, "levels:") && num_levels != -1)
4330 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4332 fputs(line, file_tmp);
4345 success = (rename(filename_tmp, filename) == 0);
4353 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4354 char *level_author, int num_levels,
4355 boolean use_artwork_set)
4357 LevelDirTree *level_info;
4362 // create user level sub-directory, if needed
4363 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4365 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4367 if (!(file = fopen(filename, MODE_WRITE)))
4369 Warn("cannot write level info file '%s'", filename);
4376 level_info = newTreeInfo();
4378 // always start with reliable default values
4379 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4381 setString(&level_info->name, level_name);
4382 setString(&level_info->author, level_author);
4383 level_info->levels = num_levels;
4384 level_info->first_level = 1;
4385 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4386 level_info->readonly = FALSE;
4388 if (use_artwork_set)
4390 level_info->graphics_set =
4391 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4392 level_info->sounds_set =
4393 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4394 level_info->music_set =
4395 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4398 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4400 fprintFileHeader(file, LEVELINFO_FILENAME);
4403 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4405 if (i == LEVELINFO_TOKEN_NAME ||
4406 i == LEVELINFO_TOKEN_AUTHOR ||
4407 i == LEVELINFO_TOKEN_LEVELS ||
4408 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4409 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4410 i == LEVELINFO_TOKEN_READONLY ||
4411 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4412 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4413 i == LEVELINFO_TOKEN_MUSIC_SET)))
4414 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4416 // just to make things nicer :)
4417 if (i == LEVELINFO_TOKEN_AUTHOR ||
4418 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4419 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4420 fprintf(file, "\n");
4423 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4427 SetFilePermissions(filename, PERMS_PRIVATE);
4429 freeTreeInfo(level_info);
4435 static void SaveUserLevelInfo(void)
4437 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4440 char *getSetupValue(int type, void *value)
4442 static char value_string[MAX_LINE_LEN];
4450 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4454 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4458 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4459 *(int *)value == FALSE ? "off" : "on"));
4463 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4466 case TYPE_YES_NO_AUTO:
4467 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4468 *(int *)value == FALSE ? "no" : "yes"));
4472 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4476 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4480 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4484 sprintf(value_string, "%d", *(int *)value);
4488 if (*(char **)value == NULL)
4491 strcpy(value_string, *(char **)value);
4495 sprintf(value_string, "player_%d", *(int *)value + 1);
4499 value_string[0] = '\0';
4503 if (type & TYPE_GHOSTED)
4504 strcpy(value_string, "n/a");
4506 return value_string;
4509 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4513 static char token_string[MAX_LINE_LEN];
4514 int token_type = token_info[token_nr].type;
4515 void *setup_value = token_info[token_nr].value;
4516 char *token_text = token_info[token_nr].text;
4517 char *value_string = getSetupValue(token_type, setup_value);
4519 // build complete token string
4520 sprintf(token_string, "%s%s", prefix, token_text);
4522 // build setup entry line
4523 line = getFormattedSetupEntry(token_string, value_string);
4525 if (token_type == TYPE_KEY_X11)
4527 Key key = *(Key *)setup_value;
4528 char *keyname = getKeyNameFromKey(key);
4530 // add comment, if useful
4531 if (!strEqual(keyname, "(undefined)") &&
4532 !strEqual(keyname, "(unknown)"))
4534 // add at least one whitespace
4536 for (i = strlen(line); i < token_comment_position; i++)
4540 strcat(line, keyname);
4547 static void InitLastPlayedLevels_ParentNode(void)
4549 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4550 LevelDirTree *leveldir_new = NULL;
4552 // check if parent node for last played levels already exists
4553 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4556 leveldir_new = newTreeInfo();
4558 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4560 leveldir_new->level_group = TRUE;
4562 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4563 setString(&leveldir_new->name, "<< (last played level sets)");
4565 pushTreeInfo(leveldir_top, leveldir_new);
4567 // create node to link back to current level directory
4568 createParentTreeInfoNode(leveldir_new);
4571 void UpdateLastPlayedLevels_TreeInfo(void)
4573 char **last_level_series = setup.level_setup.last_level_series;
4574 boolean reset_leveldir_current = FALSE;
4575 LevelDirTree *leveldir_last;
4576 TreeInfo **node_new = NULL;
4579 if (last_level_series[0] == NULL)
4582 InitLastPlayedLevels_ParentNode();
4584 // check if current level set is from "last played" sub-tree to be rebuilt
4585 reset_leveldir_current = strEqual(leveldir_current->node_parent->identifier,
4586 TOKEN_STR_LAST_LEVEL_SERIES);
4588 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4589 TOKEN_STR_LAST_LEVEL_SERIES,
4591 if (leveldir_last == NULL)
4594 node_new = &leveldir_last->node_group->next;
4596 freeTreeInfo(*node_new);
4598 for (i = 0; last_level_series[i] != NULL; i++)
4600 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4601 last_level_series[i]);
4603 *node_new = getTreeInfoCopy(node_last); // copy complete node
4605 (*node_new)->node_top = &leveldir_first; // correct top node link
4606 (*node_new)->node_parent = leveldir_last; // correct parent node link
4608 (*node_new)->node_group = NULL;
4609 (*node_new)->next = NULL;
4611 (*node_new)->cl_first = -1; // force setting tree cursor
4613 node_new = &((*node_new)->next);
4616 if (reset_leveldir_current)
4617 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4618 last_level_series[0]);
4621 static void UpdateLastPlayedLevels_List(void)
4623 char **last_level_series = setup.level_setup.last_level_series;
4624 int pos = MAX_LEVELDIR_HISTORY - 1;
4627 // search for potentially already existing entry in list of level sets
4628 for (i = 0; last_level_series[i] != NULL; i++)
4629 if (strEqual(last_level_series[i], leveldir_current->identifier))
4632 // move list of level sets one entry down (using potentially free entry)
4633 for (i = pos; i > 0; i--)
4634 setString(&last_level_series[i], last_level_series[i - 1]);
4636 // put last played level set at top position
4637 setString(&last_level_series[0], leveldir_current->identifier);
4640 void LoadLevelSetup_LastSeries(void)
4642 // --------------------------------------------------------------------------
4643 // ~/.<program>/levelsetup.conf
4644 // --------------------------------------------------------------------------
4646 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4647 SetupFileHash *level_setup_hash = NULL;
4651 // always start with reliable default values
4652 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4654 // start with empty history of last played level sets
4655 setString(&setup.level_setup.last_level_series[0], NULL);
4657 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4659 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4661 if (leveldir_current == NULL)
4662 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4665 if ((level_setup_hash = loadSetupFileHash(filename)))
4667 char *last_level_series =
4668 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4670 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4672 if (leveldir_current == NULL)
4673 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4675 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4677 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4678 LevelDirTree *leveldir_last;
4680 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4682 last_level_series = getHashEntry(level_setup_hash, token);
4684 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4686 if (leveldir_last != NULL)
4687 setString(&setup.level_setup.last_level_series[pos++],
4691 setString(&setup.level_setup.last_level_series[pos], NULL);
4693 freeSetupFileHash(level_setup_hash);
4697 Debug("setup", "using default setup values");
4703 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4705 // --------------------------------------------------------------------------
4706 // ~/.<program>/levelsetup.conf
4707 // --------------------------------------------------------------------------
4709 // check if the current level directory structure is available at this point
4710 if (leveldir_current == NULL)
4713 char **last_level_series = setup.level_setup.last_level_series;
4714 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4718 InitUserDataDirectory();
4720 UpdateLastPlayedLevels_List();
4722 if (!(file = fopen(filename, MODE_WRITE)))
4724 Warn("cannot write setup file '%s'", filename);
4731 fprintFileHeader(file, LEVELSETUP_FILENAME);
4733 if (deactivate_last_level_series)
4734 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4736 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4737 leveldir_current->identifier));
4739 for (i = 0; last_level_series[i] != NULL; i++)
4741 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4743 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4745 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4750 SetFilePermissions(filename, PERMS_PRIVATE);
4755 void SaveLevelSetup_LastSeries(void)
4757 SaveLevelSetup_LastSeries_Ext(FALSE);
4760 void SaveLevelSetup_LastSeries_Deactivate(void)
4762 SaveLevelSetup_LastSeries_Ext(TRUE);
4765 static void checkSeriesInfo(void)
4767 static char *level_directory = NULL;
4770 DirectoryEntry *dir_entry;
4773 checked_free(level_directory);
4775 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4777 level_directory = getPath2((leveldir_current->in_user_dir ?
4778 getUserLevelDir(NULL) :
4779 options.level_directory),
4780 leveldir_current->fullpath);
4782 if ((dir = openDirectory(level_directory)) == NULL)
4784 Warn("cannot read level directory '%s'", level_directory);
4790 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4792 if (strlen(dir_entry->basename) > 4 &&
4793 dir_entry->basename[3] == '.' &&
4794 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4796 char levelnum_str[4];
4799 strncpy(levelnum_str, dir_entry->basename, 3);
4800 levelnum_str[3] = '\0';
4802 levelnum_value = atoi(levelnum_str);
4804 if (levelnum_value < leveldir_current->first_level)
4806 Warn("additional level %d found", levelnum_value);
4808 leveldir_current->first_level = levelnum_value;
4810 else if (levelnum_value > leveldir_current->last_level)
4812 Warn("additional level %d found", levelnum_value);
4814 leveldir_current->last_level = levelnum_value;
4820 closeDirectory(dir);
4823 void LoadLevelSetup_SeriesInfo(void)
4826 SetupFileHash *level_setup_hash = NULL;
4827 char *level_subdir = leveldir_current->subdir;
4830 // always start with reliable default values
4831 level_nr = leveldir_current->first_level;
4833 for (i = 0; i < MAX_LEVELS; i++)
4835 LevelStats_setPlayed(i, 0);
4836 LevelStats_setSolved(i, 0);
4841 // --------------------------------------------------------------------------
4842 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4843 // --------------------------------------------------------------------------
4845 level_subdir = leveldir_current->subdir;
4847 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4849 if ((level_setup_hash = loadSetupFileHash(filename)))
4853 // get last played level in this level set
4855 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4859 level_nr = atoi(token_value);
4861 if (level_nr < leveldir_current->first_level)
4862 level_nr = leveldir_current->first_level;
4863 if (level_nr > leveldir_current->last_level)
4864 level_nr = leveldir_current->last_level;
4867 // get handicap level in this level set
4869 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4873 int level_nr = atoi(token_value);
4875 if (level_nr < leveldir_current->first_level)
4876 level_nr = leveldir_current->first_level;
4877 if (level_nr > leveldir_current->last_level + 1)
4878 level_nr = leveldir_current->last_level;
4880 if (leveldir_current->user_defined || !leveldir_current->handicap)
4881 level_nr = leveldir_current->last_level;
4883 leveldir_current->handicap_level = level_nr;
4886 // get number of played and solved levels in this level set
4888 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4890 char *token = HASH_ITERATION_TOKEN(itr);
4891 char *value = HASH_ITERATION_VALUE(itr);
4893 if (strlen(token) == 3 &&
4894 token[0] >= '0' && token[0] <= '9' &&
4895 token[1] >= '0' && token[1] <= '9' &&
4896 token[2] >= '0' && token[2] <= '9')
4898 int level_nr = atoi(token);
4901 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4903 value = strchr(value, ' ');
4906 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4909 END_HASH_ITERATION(hash, itr)
4911 freeSetupFileHash(level_setup_hash);
4915 Debug("setup", "using default setup values");
4921 void SaveLevelSetup_SeriesInfo(void)
4924 char *level_subdir = leveldir_current->subdir;
4925 char *level_nr_str = int2str(level_nr, 0);
4926 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4930 // --------------------------------------------------------------------------
4931 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4932 // --------------------------------------------------------------------------
4934 InitLevelSetupDirectory(level_subdir);
4936 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4938 if (!(file = fopen(filename, MODE_WRITE)))
4940 Warn("cannot write setup file '%s'", filename);
4947 fprintFileHeader(file, LEVELSETUP_FILENAME);
4949 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4951 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4952 handicap_level_str));
4954 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4957 if (LevelStats_getPlayed(i) > 0 ||
4958 LevelStats_getSolved(i) > 0)
4963 sprintf(token, "%03d", i);
4964 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4966 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4972 SetFilePermissions(filename, PERMS_PRIVATE);
4977 int LevelStats_getPlayed(int nr)
4979 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4982 int LevelStats_getSolved(int nr)
4984 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4987 void LevelStats_setPlayed(int nr, int value)
4989 if (nr >= 0 && nr < MAX_LEVELS)
4990 level_stats[nr].played = value;
4993 void LevelStats_setSolved(int nr, int value)
4995 if (nr >= 0 && nr < MAX_LEVELS)
4996 level_stats[nr].solved = value;
4999 void LevelStats_incPlayed(int nr)
5001 if (nr >= 0 && nr < MAX_LEVELS)
5002 level_stats[nr].played++;
5005 void LevelStats_incSolved(int nr)
5007 if (nr >= 0 && nr < MAX_LEVELS)
5008 level_stats[nr].solved++;
5011 void LoadUserSetup(void)
5013 // --------------------------------------------------------------------------
5014 // ~/.<program>/usersetup.conf
5015 // --------------------------------------------------------------------------
5017 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5018 SetupFileHash *user_setup_hash = NULL;
5020 // always start with reliable default values
5023 if ((user_setup_hash = loadSetupFileHash(filename)))
5027 // get last selected user number
5028 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5031 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5033 freeSetupFileHash(user_setup_hash);
5037 Debug("setup", "using default setup values");
5043 void SaveUserSetup(void)
5045 // --------------------------------------------------------------------------
5046 // ~/.<program>/usersetup.conf
5047 // --------------------------------------------------------------------------
5049 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5052 InitMainUserDataDirectory();
5054 if (!(file = fopen(filename, MODE_WRITE)))
5056 Warn("cannot write setup file '%s'", filename);
5063 fprintFileHeader(file, USERSETUP_FILENAME);
5065 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5069 SetFilePermissions(filename, PERMS_PRIVATE);