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] =
47 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
48 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
49 IS_LEVELCLASS_BD(n) ? FC_YELLOW : \
50 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
51 IS_LEVELCLASS_SP(n) ? FC_YELLOW : \
52 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
53 IS_LEVELCLASS_SB(n) ? FC_YELLOW : \
54 IS_LEVELCLASS_CONTRIB(n) ? FC_GREEN : \
55 IS_LEVELCLASS_PRIVATE(n) ? FC_RED : \
58 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
59 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
60 IS_LEVELCLASS_BD(n) ? 2 : \
61 IS_LEVELCLASS_EM(n) ? 3 : \
62 IS_LEVELCLASS_SP(n) ? 4 : \
63 IS_LEVELCLASS_DX(n) ? 5 : \
64 IS_LEVELCLASS_SB(n) ? 6 : \
65 IS_LEVELCLASS_CONTRIB(n) ? 7 : \
66 IS_LEVELCLASS_PRIVATE(n) ? 8 : \
69 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
70 IS_ARTWORKCLASS_CONTRIB(n) ? FC_GREEN : \
71 IS_ARTWORKCLASS_PRIVATE(n) ? FC_RED : \
72 IS_ARTWORKCLASS_LEVEL(n) ? FC_YELLOW : \
75 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
76 IS_ARTWORKCLASS_LEVEL(n) ? 1 : \
77 IS_ARTWORKCLASS_CONTRIB(n) ? 2 : \
78 IS_ARTWORKCLASS_PRIVATE(n) ? 3 : \
81 #define TOKEN_VALUE_POSITION_SHORT 32
82 #define TOKEN_VALUE_POSITION_DEFAULT 40
83 #define TOKEN_COMMENT_POSITION_DEFAULT 60
85 #define MAX_COOKIE_LEN 256
88 static void setTreeInfoToDefaults(TreeInfo *, int);
89 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
90 static int compareTreeInfoEntries(const void *, const void *);
92 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
93 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
95 static SetupFileHash *artworkinfo_cache_old = NULL;
96 static SetupFileHash *artworkinfo_cache_new = NULL;
97 static SetupFileHash *optional_tokens_hash = NULL;
98 static boolean use_artworkinfo_cache = TRUE;
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(getUserGameDataDir(), 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 *getLevelSetupDir(char *level_subdir)
143 static char *levelsetup_dir = NULL;
144 char *data_dir = getUserGameDataDir();
145 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
147 checked_free(levelsetup_dir);
149 if (level_subdir != NULL)
150 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
152 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
154 return levelsetup_dir;
157 static char *getCacheDir(void)
159 static char *cache_dir = NULL;
161 if (cache_dir == NULL)
162 cache_dir = getPath2(getUserGameDataDir(), CACHE_DIRECTORY);
167 static char *getNetworkDir(void)
169 static char *network_dir = NULL;
171 if (network_dir == NULL)
172 network_dir = getPath2(getUserGameDataDir(), NETWORK_DIRECTORY);
177 char *getLevelDirFromTreeInfo(TreeInfo *node)
179 static char *level_dir = NULL;
182 return options.level_directory;
184 checked_free(level_dir);
186 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
187 options.level_directory), node->fullpath);
192 char *getUserLevelDir(char *level_subdir)
194 static char *userlevel_dir = NULL;
195 char *data_dir = getUserGameDataDir();
196 char *userlevel_subdir = LEVELS_DIRECTORY;
198 checked_free(userlevel_dir);
200 if (level_subdir != NULL)
201 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
203 userlevel_dir = getPath2(data_dir, userlevel_subdir);
205 return userlevel_dir;
208 char *getNetworkLevelDir(char *level_subdir)
210 static char *network_level_dir = NULL;
211 char *data_dir = getNetworkDir();
212 char *networklevel_subdir = LEVELS_DIRECTORY;
214 checked_free(network_level_dir);
216 if (level_subdir != NULL)
217 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
219 network_level_dir = getPath2(data_dir, networklevel_subdir);
221 return network_level_dir;
224 char *getCurrentLevelDir(void)
226 return getLevelDirFromTreeInfo(leveldir_current);
229 char *getNewUserLevelSubdir(void)
231 static char *new_level_subdir = NULL;
232 char *subdir_prefix = getLoginName();
233 char subdir_suffix[10];
234 int max_suffix_number = 1000;
237 while (++i < max_suffix_number)
239 sprintf(subdir_suffix, "_%d", i);
241 checked_free(new_level_subdir);
242 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
244 if (!directoryExists(getUserLevelDir(new_level_subdir)))
248 return new_level_subdir;
251 static char *getTapeDir(char *level_subdir)
253 static char *tape_dir = NULL;
254 char *data_dir = getUserGameDataDir();
255 char *tape_subdir = TAPES_DIRECTORY;
257 checked_free(tape_dir);
259 if (level_subdir != NULL)
260 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
262 tape_dir = getPath2(data_dir, tape_subdir);
267 static char *getSolutionTapeDir(void)
269 static char *tape_dir = NULL;
270 char *data_dir = getCurrentLevelDir();
271 char *tape_subdir = TAPES_DIRECTORY;
273 checked_free(tape_dir);
275 tape_dir = getPath2(data_dir, tape_subdir);
280 static char *getDefaultGraphicsDir(char *graphics_subdir)
282 static char *graphics_dir = NULL;
284 if (graphics_subdir == NULL)
285 return options.graphics_directory;
287 checked_free(graphics_dir);
289 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
294 static char *getDefaultSoundsDir(char *sounds_subdir)
296 static char *sounds_dir = NULL;
298 if (sounds_subdir == NULL)
299 return options.sounds_directory;
301 checked_free(sounds_dir);
303 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
308 static char *getDefaultMusicDir(char *music_subdir)
310 static char *music_dir = NULL;
312 if (music_subdir == NULL)
313 return options.music_directory;
315 checked_free(music_dir);
317 music_dir = getPath2(options.music_directory, music_subdir);
322 static char *getClassicArtworkSet(int type)
324 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
325 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
326 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
329 static char *getClassicArtworkDir(int type)
331 return (type == TREE_TYPE_GRAPHICS_DIR ?
332 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
333 type == TREE_TYPE_SOUNDS_DIR ?
334 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
335 type == TREE_TYPE_MUSIC_DIR ?
336 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
339 char *getUserGraphicsDir(void)
341 static char *usergraphics_dir = NULL;
343 if (usergraphics_dir == NULL)
344 usergraphics_dir = getPath2(getUserGameDataDir(), GRAPHICS_DIRECTORY);
346 return usergraphics_dir;
349 char *getUserSoundsDir(void)
351 static char *usersounds_dir = NULL;
353 if (usersounds_dir == NULL)
354 usersounds_dir = getPath2(getUserGameDataDir(), SOUNDS_DIRECTORY);
356 return usersounds_dir;
359 char *getUserMusicDir(void)
361 static char *usermusic_dir = NULL;
363 if (usermusic_dir == NULL)
364 usermusic_dir = getPath2(getUserGameDataDir(), MUSIC_DIRECTORY);
366 return usermusic_dir;
369 static char *getSetupArtworkDir(TreeInfo *ti)
371 static char *artwork_dir = NULL;
376 checked_free(artwork_dir);
378 artwork_dir = getPath2(ti->basepath, ti->fullpath);
383 char *setLevelArtworkDir(TreeInfo *ti)
385 char **artwork_path_ptr, **artwork_set_ptr;
386 TreeInfo *level_artwork;
388 if (ti == NULL || leveldir_current == NULL)
391 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
392 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
394 checked_free(*artwork_path_ptr);
396 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
398 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
403 No (or non-existing) artwork configured in "levelinfo.conf". This would
404 normally result in using the artwork configured in the setup menu. But
405 if an artwork subdirectory exists (which might contain custom artwork
406 or an artwork configuration file), this level artwork must be treated
407 as relative to the default "classic" artwork, not to the artwork that
408 is currently configured in the setup menu.
410 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
411 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
412 the real "classic" artwork from the original R'n'D (like "gfx_classic").
415 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
417 checked_free(*artwork_set_ptr);
419 if (directoryExists(dir))
421 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
422 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
426 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
427 *artwork_set_ptr = NULL;
433 return *artwork_set_ptr;
436 static char *getLevelArtworkSet(int type)
438 if (leveldir_current == NULL)
441 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
444 static char *getLevelArtworkDir(int type)
446 if (leveldir_current == NULL)
447 return UNDEFINED_FILENAME;
449 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
452 char *getProgramMainDataPath(char *command_filename, char *base_path)
454 // check if the program's main data base directory is configured
455 if (!strEqual(base_path, "."))
456 return getStringCopy(base_path);
458 /* if the program is configured to start from current directory (default),
459 determine program package directory from program binary (some versions
460 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
461 set the current working directory to the program package directory) */
462 char *main_data_path = getBasePath(command_filename);
464 #if defined(PLATFORM_MACOSX)
465 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
467 char *main_data_path_old = main_data_path;
469 // cut relative path to Mac OS X application binary directory from path
470 main_data_path[strlen(main_data_path) -
471 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
473 // cut trailing path separator from path (but not if path is root directory)
474 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
475 main_data_path[strlen(main_data_path) - 1] = '\0';
477 // replace empty path with current directory
478 if (strEqual(main_data_path, ""))
479 main_data_path = ".";
481 // add relative path to Mac OS X application resources directory to path
482 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
484 free(main_data_path_old);
488 return main_data_path;
491 char *getProgramConfigFilename(char *command_filename)
493 static char *config_filename_1 = NULL;
494 static char *config_filename_2 = NULL;
495 static char *config_filename_3 = NULL;
496 static boolean initialized = FALSE;
500 char *command_filename_1 = getStringCopy(command_filename);
502 // strip trailing executable suffix from command filename
503 if (strSuffix(command_filename_1, ".exe"))
504 command_filename_1[strlen(command_filename_1) - 4] = '\0';
506 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
507 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
509 char *command_basepath = getBasePath(command_filename);
510 char *command_basename = getBaseNameNoSuffix(command_filename);
511 char *command_filename_2 = getPath2(command_basepath, command_basename);
513 config_filename_1 = getStringCat2(command_filename_1, ".conf");
514 config_filename_2 = getStringCat2(command_filename_2, ".conf");
515 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
517 checked_free(ro_base_path);
518 checked_free(conf_directory);
520 checked_free(command_basepath);
521 checked_free(command_basename);
523 checked_free(command_filename_1);
524 checked_free(command_filename_2);
529 // 1st try: look for config file that exactly matches the binary filename
530 if (fileExists(config_filename_1))
531 return config_filename_1;
533 // 2nd try: look for config file that matches binary filename without suffix
534 if (fileExists(config_filename_2))
535 return config_filename_2;
537 // 3rd try: return setup config filename in global program config directory
538 return config_filename_3;
541 char *getTapeFilename(int nr)
543 static char *filename = NULL;
544 char basename[MAX_FILENAME_LEN];
546 checked_free(filename);
548 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
549 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
554 char *getSolutionTapeFilename(int nr)
556 static char *filename = NULL;
557 char basename[MAX_FILENAME_LEN];
559 checked_free(filename);
561 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
562 filename = getPath2(getSolutionTapeDir(), basename);
564 if (!fileExists(filename))
566 static char *filename_sln = NULL;
568 checked_free(filename_sln);
570 sprintf(basename, "%03d.sln", nr);
571 filename_sln = getPath2(getSolutionTapeDir(), basename);
573 if (fileExists(filename_sln))
580 char *getScoreFilename(int nr)
582 static char *filename = NULL;
583 char basename[MAX_FILENAME_LEN];
585 checked_free(filename);
587 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
589 // used instead of "leveldir_current->subdir" (for network games)
590 filename = getPath2(getScoreDir(levelset.identifier), basename);
595 char *getSetupFilename(void)
597 static char *filename = NULL;
599 checked_free(filename);
601 filename = getPath2(getSetupDir(), SETUP_FILENAME);
606 char *getDefaultSetupFilename(void)
608 return program.config_filename;
611 char *getEditorSetupFilename(void)
613 static char *filename = NULL;
615 checked_free(filename);
616 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
618 if (fileExists(filename))
621 checked_free(filename);
622 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
627 char *getHelpAnimFilename(void)
629 static char *filename = NULL;
631 checked_free(filename);
633 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
638 char *getHelpTextFilename(void)
640 static char *filename = NULL;
642 checked_free(filename);
644 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
649 char *getLevelSetInfoFilename(void)
651 static char *filename = NULL;
666 for (i = 0; basenames[i] != NULL; i++)
668 checked_free(filename);
669 filename = getPath2(getCurrentLevelDir(), basenames[i]);
671 if (fileExists(filename))
678 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
680 static char basename[32];
682 sprintf(basename, "%s_%d.txt",
683 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
688 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
690 static char *filename = NULL;
692 boolean skip_setup_artwork = FALSE;
694 checked_free(filename);
696 basename = getLevelSetTitleMessageBasename(nr, initial);
698 if (!gfx.override_level_graphics)
700 // 1st try: look for special artwork in current level series directory
701 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
702 if (fileExists(filename))
707 // 2nd try: look for message file in current level set directory
708 filename = getPath2(getCurrentLevelDir(), basename);
709 if (fileExists(filename))
714 // check if there is special artwork configured in level series config
715 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
717 // 3rd try: look for special artwork configured in level series config
718 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
719 if (fileExists(filename))
724 // take missing artwork configured in level set config from default
725 skip_setup_artwork = TRUE;
729 if (!skip_setup_artwork)
731 // 4th try: look for special artwork in configured artwork directory
732 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
733 if (fileExists(filename))
739 // 5th try: look for default artwork in new default artwork directory
740 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
741 if (fileExists(filename))
746 // 6th try: look for default artwork in old default artwork directory
747 filename = getPath2(options.graphics_directory, basename);
748 if (fileExists(filename))
751 return NULL; // cannot find specified artwork file anywhere
754 static char *getCorrectedArtworkBasename(char *basename)
759 char *getCustomImageFilename(char *basename)
761 static char *filename = NULL;
762 boolean skip_setup_artwork = FALSE;
764 checked_free(filename);
766 basename = getCorrectedArtworkBasename(basename);
768 if (!gfx.override_level_graphics)
770 // 1st try: look for special artwork in current level series directory
771 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
772 if (fileExists(filename))
777 // check if there is special artwork configured in level series config
778 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
780 // 2nd try: look for special artwork configured in level series config
781 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
782 if (fileExists(filename))
787 // take missing artwork configured in level set config from default
788 skip_setup_artwork = TRUE;
792 if (!skip_setup_artwork)
794 // 3rd try: look for special artwork in configured artwork directory
795 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
796 if (fileExists(filename))
802 // 4th try: look for default artwork in new default artwork directory
803 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
804 if (fileExists(filename))
809 // 5th try: look for default artwork in old default artwork directory
810 filename = getImg2(options.graphics_directory, basename);
811 if (fileExists(filename))
814 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
818 Warn("cannot find artwork file '%s' (using fallback)", basename);
820 // 6th try: look for fallback artwork in old default artwork directory
821 // (needed to prevent errors when trying to access unused artwork files)
822 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
823 if (fileExists(filename))
827 return NULL; // cannot find specified artwork file anywhere
830 char *getCustomSoundFilename(char *basename)
832 static char *filename = NULL;
833 boolean skip_setup_artwork = FALSE;
835 checked_free(filename);
837 basename = getCorrectedArtworkBasename(basename);
839 if (!gfx.override_level_sounds)
841 // 1st try: look for special artwork in current level series directory
842 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
843 if (fileExists(filename))
848 // check if there is special artwork configured in level series config
849 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
851 // 2nd try: look for special artwork configured in level series config
852 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
853 if (fileExists(filename))
858 // take missing artwork configured in level set config from default
859 skip_setup_artwork = TRUE;
863 if (!skip_setup_artwork)
865 // 3rd try: look for special artwork in configured artwork directory
866 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
867 if (fileExists(filename))
873 // 4th try: look for default artwork in new default artwork directory
874 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
875 if (fileExists(filename))
880 // 5th try: look for default artwork in old default artwork directory
881 filename = getPath2(options.sounds_directory, basename);
882 if (fileExists(filename))
885 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
889 Warn("cannot find artwork file '%s' (using fallback)", basename);
891 // 6th try: look for fallback artwork in old default artwork directory
892 // (needed to prevent errors when trying to access unused artwork files)
893 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
894 if (fileExists(filename))
898 return NULL; // cannot find specified artwork file anywhere
901 char *getCustomMusicFilename(char *basename)
903 static char *filename = NULL;
904 boolean skip_setup_artwork = FALSE;
906 checked_free(filename);
908 basename = getCorrectedArtworkBasename(basename);
910 if (!gfx.override_level_music)
912 // 1st try: look for special artwork in current level series directory
913 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
914 if (fileExists(filename))
919 // check if there is special artwork configured in level series config
920 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
922 // 2nd try: look for special artwork configured in level series config
923 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
924 if (fileExists(filename))
929 // take missing artwork configured in level set config from default
930 skip_setup_artwork = TRUE;
934 if (!skip_setup_artwork)
936 // 3rd try: look for special artwork in configured artwork directory
937 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
938 if (fileExists(filename))
944 // 4th try: look for default artwork in new default artwork directory
945 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
946 if (fileExists(filename))
951 // 5th try: look for default artwork in old default artwork directory
952 filename = getPath2(options.music_directory, basename);
953 if (fileExists(filename))
956 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
960 Warn("cannot find artwork file '%s' (using fallback)", basename);
962 // 6th try: look for fallback artwork in old default artwork directory
963 // (needed to prevent errors when trying to access unused artwork files)
964 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
965 if (fileExists(filename))
969 return NULL; // cannot find specified artwork file anywhere
972 char *getCustomArtworkFilename(char *basename, int type)
974 if (type == ARTWORK_TYPE_GRAPHICS)
975 return getCustomImageFilename(basename);
976 else if (type == ARTWORK_TYPE_SOUNDS)
977 return getCustomSoundFilename(basename);
978 else if (type == ARTWORK_TYPE_MUSIC)
979 return getCustomMusicFilename(basename);
981 return UNDEFINED_FILENAME;
984 char *getCustomArtworkConfigFilename(int type)
986 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
989 char *getCustomArtworkLevelConfigFilename(int type)
991 static char *filename = NULL;
993 checked_free(filename);
995 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1000 char *getCustomMusicDirectory(void)
1002 static char *directory = NULL;
1003 boolean skip_setup_artwork = FALSE;
1005 checked_free(directory);
1007 if (!gfx.override_level_music)
1009 // 1st try: look for special artwork in current level series directory
1010 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1011 if (directoryExists(directory))
1016 // check if there is special artwork configured in level series config
1017 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1019 // 2nd try: look for special artwork configured in level series config
1020 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1021 if (directoryExists(directory))
1026 // take missing artwork configured in level set config from default
1027 skip_setup_artwork = TRUE;
1031 if (!skip_setup_artwork)
1033 // 3rd try: look for special artwork in configured artwork directory
1034 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1035 if (directoryExists(directory))
1041 // 4th try: look for default artwork in new default artwork directory
1042 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1043 if (directoryExists(directory))
1048 // 5th try: look for default artwork in old default artwork directory
1049 directory = getStringCopy(options.music_directory);
1050 if (directoryExists(directory))
1053 return NULL; // cannot find specified artwork file anywhere
1056 void InitTapeDirectory(char *level_subdir)
1058 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1059 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1060 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1063 void InitScoreDirectory(char *level_subdir)
1065 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1067 if (program.global_scores)
1068 createDirectory(getCommonDataDir(), "common data", permissions);
1070 createDirectory(getUserGameDataDir(), "user data", permissions);
1072 createDirectory(getScoreDir(NULL), "main score", permissions);
1073 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1076 static void SaveUserLevelInfo(void);
1078 void InitUserLevelDirectory(char *level_subdir)
1080 if (!directoryExists(getUserLevelDir(level_subdir)))
1082 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1083 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1084 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1086 if (setup.internal.create_user_levelset)
1087 SaveUserLevelInfo();
1091 void InitNetworkLevelDirectory(char *level_subdir)
1093 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1095 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1096 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1097 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1098 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1102 void InitLevelSetupDirectory(char *level_subdir)
1104 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1105 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1106 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1109 static void InitCacheDirectory(void)
1111 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1112 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1116 // ----------------------------------------------------------------------------
1117 // some functions to handle lists of level and artwork directories
1118 // ----------------------------------------------------------------------------
1120 TreeInfo *newTreeInfo(void)
1122 return checked_calloc(sizeof(TreeInfo));
1125 TreeInfo *newTreeInfo_setDefaults(int type)
1127 TreeInfo *ti = newTreeInfo();
1129 setTreeInfoToDefaults(ti, type);
1134 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1136 node_new->next = *node_first;
1137 *node_first = node_new;
1140 int numTreeInfo(TreeInfo *node)
1153 boolean validLevelSeries(TreeInfo *node)
1155 return (node != NULL && !node->node_group && !node->parent_link);
1158 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1163 if (node->node_group) // enter level group (step down into tree)
1164 return getFirstValidTreeInfoEntry(node->node_group);
1165 else if (node->parent_link) // skip start entry of level group
1167 if (node->next) // get first real level series entry
1168 return getFirstValidTreeInfoEntry(node->next);
1169 else // leave empty level group and go on
1170 return getFirstValidTreeInfoEntry(node->node_parent->next);
1172 else // this seems to be a regular level series
1176 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1181 if (node->node_parent == NULL) // top level group
1182 return *node->node_top;
1183 else // sub level group
1184 return node->node_parent->node_group;
1187 int numTreeInfoInGroup(TreeInfo *node)
1189 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1192 int posTreeInfo(TreeInfo *node)
1194 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1199 if (node_cmp == node)
1203 node_cmp = node_cmp->next;
1209 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1211 TreeInfo *node_default = node;
1223 return node_default;
1226 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1228 if (identifier == NULL)
1233 if (node->node_group)
1235 TreeInfo *node_group;
1237 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
1242 else if (!node->parent_link)
1244 if (strEqual(identifier, node->identifier))
1254 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1255 TreeInfo *node, boolean skip_sets_without_levels)
1262 if (!node->parent_link && !node->level_group &&
1263 skip_sets_without_levels && node->levels == 0)
1264 return cloneTreeNode(node_top, node_parent, node->next,
1265 skip_sets_without_levels);
1267 node_new = getTreeInfoCopy(node); // copy complete node
1269 node_new->node_top = node_top; // correct top node link
1270 node_new->node_parent = node_parent; // correct parent node link
1272 if (node->level_group)
1273 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1274 skip_sets_without_levels);
1276 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1277 skip_sets_without_levels);
1282 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1284 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1286 *ti_new = ti_cloned;
1289 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1291 boolean settings_changed = FALSE;
1295 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1296 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1297 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1298 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1299 char *graphics_set = NULL;
1301 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1302 graphics_set = node->graphics_set_ecs;
1304 if (node->graphics_set_aga && (want_aga || has_only_aga))
1305 graphics_set = node->graphics_set_aga;
1307 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1309 setString(&node->graphics_set, graphics_set);
1310 settings_changed = TRUE;
1313 if (node->node_group != NULL)
1314 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1319 return settings_changed;
1322 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1324 boolean settings_changed = FALSE;
1328 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1329 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1330 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1331 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1332 char *sounds_set = NULL;
1334 if (node->sounds_set_default && (want_default || has_only_default))
1335 sounds_set = node->sounds_set_default;
1337 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1338 sounds_set = node->sounds_set_lowpass;
1340 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1342 setString(&node->sounds_set, sounds_set);
1343 settings_changed = TRUE;
1346 if (node->node_group != NULL)
1347 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1352 return settings_changed;
1355 void dumpTreeInfo(TreeInfo *node, int depth)
1359 Debug("tree", "Dumping TreeInfo:");
1363 for (i = 0; i < (depth + 1) * 3; i++)
1364 DebugContinued("", " ");
1366 DebugContinued("tree", "'%s' / '%s'\n", node->identifier, node->name);
1369 // use for dumping artwork info tree
1370 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1371 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1374 if (node->node_group != NULL)
1375 dumpTreeInfo(node->node_group, depth + 1);
1381 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1382 int (*compare_function)(const void *,
1385 int num_nodes = numTreeInfo(*node_first);
1386 TreeInfo **sort_array;
1387 TreeInfo *node = *node_first;
1393 // allocate array for sorting structure pointers
1394 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1396 // writing structure pointers to sorting array
1397 while (i < num_nodes && node) // double boundary check...
1399 sort_array[i] = node;
1405 // sorting the structure pointers in the sorting array
1406 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1409 // update the linkage of list elements with the sorted node array
1410 for (i = 0; i < num_nodes - 1; i++)
1411 sort_array[i]->next = sort_array[i + 1];
1412 sort_array[num_nodes - 1]->next = NULL;
1414 // update the linkage of the main list anchor pointer
1415 *node_first = sort_array[0];
1419 // now recursively sort the level group structures
1423 if (node->node_group != NULL)
1424 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1430 void sortTreeInfo(TreeInfo **node_first)
1432 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1436 // ============================================================================
1437 // some stuff from "files.c"
1438 // ============================================================================
1440 #if defined(PLATFORM_WIN32)
1442 #define S_IRGRP S_IRUSR
1445 #define S_IROTH S_IRUSR
1448 #define S_IWGRP S_IWUSR
1451 #define S_IWOTH S_IWUSR
1454 #define S_IXGRP S_IXUSR
1457 #define S_IXOTH S_IXUSR
1460 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1465 #endif // PLATFORM_WIN32
1467 // file permissions for newly written files
1468 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1469 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1470 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1472 #define MODE_W_PRIVATE (S_IWUSR)
1473 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1474 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1476 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1477 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1478 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1480 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1481 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1482 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1485 char *getHomeDir(void)
1487 static char *dir = NULL;
1489 #if defined(PLATFORM_WIN32)
1492 dir = checked_malloc(MAX_PATH + 1);
1494 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1497 #elif defined(PLATFORM_UNIX)
1500 if ((dir = getenv("HOME")) == NULL)
1502 dir = getUnixHomeDir();
1505 dir = getStringCopy(dir);
1517 char *getCommonDataDir(void)
1519 static char *common_data_dir = NULL;
1521 #if defined(PLATFORM_WIN32)
1522 if (common_data_dir == NULL)
1524 char *dir = checked_malloc(MAX_PATH + 1);
1526 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1527 && !strEqual(dir, "")) // empty for Windows 95/98
1528 common_data_dir = getPath2(dir, program.userdata_subdir);
1530 common_data_dir = options.rw_base_directory;
1533 if (common_data_dir == NULL)
1534 common_data_dir = options.rw_base_directory;
1537 return common_data_dir;
1540 char *getPersonalDataDir(void)
1542 static char *personal_data_dir = NULL;
1544 #if defined(PLATFORM_MACOSX)
1545 if (personal_data_dir == NULL)
1546 personal_data_dir = getPath2(getHomeDir(), "Documents");
1548 if (personal_data_dir == NULL)
1549 personal_data_dir = getHomeDir();
1552 return personal_data_dir;
1555 char *getUserGameDataDir(void)
1557 static char *user_game_data_dir = NULL;
1559 #if defined(PLATFORM_ANDROID)
1560 if (user_game_data_dir == NULL)
1561 user_game_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1562 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1563 SDL_AndroidGetExternalStoragePath() :
1564 SDL_AndroidGetInternalStoragePath());
1566 if (user_game_data_dir == NULL)
1567 user_game_data_dir = getPath2(getPersonalDataDir(),
1568 program.userdata_subdir);
1571 return user_game_data_dir;
1574 char *getSetupDir(void)
1576 return getUserGameDataDir();
1579 static mode_t posix_umask(mode_t mask)
1581 #if defined(PLATFORM_UNIX)
1588 static int posix_mkdir(const char *pathname, mode_t mode)
1590 #if defined(PLATFORM_WIN32)
1591 return mkdir(pathname);
1593 return mkdir(pathname, mode);
1597 static boolean posix_process_running_setgid(void)
1599 #if defined(PLATFORM_UNIX)
1600 return (getgid() != getegid());
1606 void createDirectory(char *dir, char *text, int permission_class)
1608 if (directoryExists(dir))
1611 // leave "other" permissions in umask untouched, but ensure group parts
1612 // of USERDATA_DIR_MODE are not masked
1613 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1614 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1615 mode_t last_umask = posix_umask(0);
1616 mode_t group_umask = ~(dir_mode & S_IRWXG);
1617 int running_setgid = posix_process_running_setgid();
1619 if (permission_class == PERMS_PUBLIC)
1621 // if we're setgid, protect files against "other"
1622 // else keep umask(0) to make the dir world-writable
1625 posix_umask(last_umask & group_umask);
1627 dir_mode = DIR_PERMS_PUBLIC_ALL;
1630 if (posix_mkdir(dir, dir_mode) != 0)
1631 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1633 if (permission_class == PERMS_PUBLIC && !running_setgid)
1634 chmod(dir, dir_mode);
1636 posix_umask(last_umask); // restore previous umask
1639 void InitUserDataDirectory(void)
1641 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1644 void SetFilePermissions(char *filename, int permission_class)
1646 int running_setgid = posix_process_running_setgid();
1647 int perms = (permission_class == PERMS_PRIVATE ?
1648 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1650 if (permission_class == PERMS_PUBLIC && !running_setgid)
1651 perms = FILE_PERMS_PUBLIC_ALL;
1653 chmod(filename, perms);
1656 char *getCookie(char *file_type)
1658 static char cookie[MAX_COOKIE_LEN + 1];
1660 if (strlen(program.cookie_prefix) + 1 +
1661 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1662 return "[COOKIE ERROR]"; // should never happen
1664 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1665 program.cookie_prefix, file_type,
1666 program.version_super, program.version_major);
1671 void fprintFileHeader(FILE *file, char *basename)
1673 char *prefix = "# ";
1676 fprintf_line_with_prefix(file, prefix, sep1, 77);
1677 fprintf(file, "%s%s\n", prefix, basename);
1678 fprintf_line_with_prefix(file, prefix, sep1, 77);
1679 fprintf(file, "\n");
1682 int getFileVersionFromCookieString(const char *cookie)
1684 const char *ptr_cookie1, *ptr_cookie2;
1685 const char *pattern1 = "_FILE_VERSION_";
1686 const char *pattern2 = "?.?";
1687 const int len_cookie = strlen(cookie);
1688 const int len_pattern1 = strlen(pattern1);
1689 const int len_pattern2 = strlen(pattern2);
1690 const int len_pattern = len_pattern1 + len_pattern2;
1691 int version_super, version_major;
1693 if (len_cookie <= len_pattern)
1696 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1697 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1699 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1702 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1703 ptr_cookie2[1] != '.' ||
1704 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1707 version_super = ptr_cookie2[0] - '0';
1708 version_major = ptr_cookie2[2] - '0';
1710 return VERSION_IDENT(version_super, version_major, 0, 0);
1713 boolean checkCookieString(const char *cookie, const char *template)
1715 const char *pattern = "_FILE_VERSION_?.?";
1716 const int len_cookie = strlen(cookie);
1717 const int len_template = strlen(template);
1718 const int len_pattern = strlen(pattern);
1720 if (len_cookie != len_template)
1723 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1730 // ----------------------------------------------------------------------------
1731 // setup file list and hash handling functions
1732 // ----------------------------------------------------------------------------
1734 char *getFormattedSetupEntry(char *token, char *value)
1737 static char entry[MAX_LINE_LEN];
1739 // if value is an empty string, just return token without value
1743 // start with the token and some spaces to format output line
1744 sprintf(entry, "%s:", token);
1745 for (i = strlen(entry); i < token_value_position; i++)
1748 // continue with the token's value
1749 strcat(entry, value);
1754 SetupFileList *newSetupFileList(char *token, char *value)
1756 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1758 new->token = getStringCopy(token);
1759 new->value = getStringCopy(value);
1766 void freeSetupFileList(SetupFileList *list)
1771 checked_free(list->token);
1772 checked_free(list->value);
1775 freeSetupFileList(list->next);
1780 char *getListEntry(SetupFileList *list, char *token)
1785 if (strEqual(list->token, token))
1788 return getListEntry(list->next, token);
1791 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1796 if (strEqual(list->token, token))
1798 checked_free(list->value);
1800 list->value = getStringCopy(value);
1804 else if (list->next == NULL)
1805 return (list->next = newSetupFileList(token, value));
1807 return setListEntry(list->next, token, value);
1810 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1815 if (list->next == NULL)
1816 return (list->next = newSetupFileList(token, value));
1818 return addListEntry(list->next, token, value);
1821 #if ENABLE_UNUSED_CODE
1823 static void printSetupFileList(SetupFileList *list)
1828 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1829 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1831 printSetupFileList(list->next);
1837 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1838 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1839 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1840 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1842 #define insert_hash_entry hashtable_insert
1843 #define search_hash_entry hashtable_search
1844 #define change_hash_entry hashtable_change
1845 #define remove_hash_entry hashtable_remove
1848 unsigned int get_hash_from_key(void *key)
1853 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1854 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1855 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1856 it works better than many other constants, prime or not) has never been
1857 adequately explained.
1859 If you just want to have a good hash function, and cannot wait, djb2
1860 is one of the best string hash functions i know. It has excellent
1861 distribution and speed on many different sets of keys and table sizes.
1862 You are not likely to do better with one of the "well known" functions
1863 such as PJW, K&R, etc.
1865 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1868 char *str = (char *)key;
1869 unsigned int hash = 5381;
1872 while ((c = *str++))
1873 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1878 static int keys_are_equal(void *key1, void *key2)
1880 return (strEqual((char *)key1, (char *)key2));
1883 SetupFileHash *newSetupFileHash(void)
1885 SetupFileHash *new_hash =
1886 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1888 if (new_hash == NULL)
1889 Fail("create_hashtable() failed -- out of memory");
1894 void freeSetupFileHash(SetupFileHash *hash)
1899 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1902 char *getHashEntry(SetupFileHash *hash, char *token)
1907 return search_hash_entry(hash, token);
1910 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1917 value_copy = getStringCopy(value);
1919 // change value; if it does not exist, insert it as new
1920 if (!change_hash_entry(hash, token, value_copy))
1921 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1922 Fail("cannot insert into hash -- aborting");
1925 char *removeHashEntry(SetupFileHash *hash, char *token)
1930 return remove_hash_entry(hash, token);
1933 #if ENABLE_UNUSED_CODE
1935 static void printSetupFileHash(SetupFileHash *hash)
1937 BEGIN_HASH_ITERATION(hash, itr)
1939 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1940 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
1942 END_HASH_ITERATION(hash, itr)
1947 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
1948 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
1949 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
1951 static boolean token_value_separator_found = FALSE;
1952 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
1953 static boolean token_value_separator_warning = FALSE;
1955 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
1956 static boolean token_already_exists_warning = FALSE;
1959 static boolean getTokenValueFromSetupLineExt(char *line,
1960 char **token_ptr, char **value_ptr,
1961 char *filename, char *line_raw,
1963 boolean separator_required)
1965 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
1966 char *token, *value, *line_ptr;
1968 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
1969 if (line_raw == NULL)
1971 strncpy(line_copy, line, MAX_LINE_LEN);
1972 line_copy[MAX_LINE_LEN] = '\0';
1975 strcpy(line_raw_copy, line_copy);
1976 line_raw = line_raw_copy;
1979 // cut trailing comment from input line
1980 for (line_ptr = line; *line_ptr; line_ptr++)
1982 if (*line_ptr == '#')
1989 // cut trailing whitespaces from input line
1990 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
1991 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
1994 // ignore empty lines
1998 // cut leading whitespaces from token
1999 for (token = line; *token; token++)
2000 if (*token != ' ' && *token != '\t')
2003 // start with empty value as reliable default
2006 token_value_separator_found = FALSE;
2008 // find end of token to determine start of value
2009 for (line_ptr = token; *line_ptr; line_ptr++)
2011 // first look for an explicit token/value separator, like ':' or '='
2012 if (*line_ptr == ':' || *line_ptr == '=')
2014 *line_ptr = '\0'; // terminate token string
2015 value = line_ptr + 1; // set beginning of value
2017 token_value_separator_found = TRUE;
2023 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2024 // fallback: if no token/value separator found, also allow whitespaces
2025 if (!token_value_separator_found && !separator_required)
2027 for (line_ptr = token; *line_ptr; line_ptr++)
2029 if (*line_ptr == ' ' || *line_ptr == '\t')
2031 *line_ptr = '\0'; // terminate token string
2032 value = line_ptr + 1; // set beginning of value
2034 token_value_separator_found = TRUE;
2040 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2041 if (token_value_separator_found)
2043 if (!token_value_separator_warning)
2045 Debug("setup", "---");
2047 if (filename != NULL)
2049 Debug("setup", "missing token/value separator(s) in config file:");
2050 Debug("setup", "- config file: '%s'", filename);
2054 Debug("setup", "missing token/value separator(s):");
2057 token_value_separator_warning = TRUE;
2060 if (filename != NULL)
2061 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2063 Debug("setup", "- line: '%s'", line_raw);
2069 // cut trailing whitespaces from token
2070 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2071 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2074 // cut leading whitespaces from value
2075 for (; *value; value++)
2076 if (*value != ' ' && *value != '\t')
2085 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2087 // while the internal (old) interface does not require a token/value
2088 // separator (for downwards compatibility with existing files which
2089 // don't use them), it is mandatory for the external (new) interface
2091 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2094 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2095 boolean top_recursion_level, boolean is_hash)
2097 static SetupFileHash *include_filename_hash = NULL;
2098 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2099 char *token, *value, *line_ptr;
2100 void *insert_ptr = NULL;
2101 boolean read_continued_line = FALSE;
2103 int line_nr = 0, token_count = 0, include_count = 0;
2105 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2106 token_value_separator_warning = FALSE;
2109 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2110 token_already_exists_warning = FALSE;
2113 if (!(file = openFile(filename, MODE_READ)))
2115 #if DEBUG_NO_CONFIG_FILE
2116 Debug("setup", "cannot open configuration file '%s'", filename);
2122 // use "insert pointer" to store list end for constant insertion complexity
2124 insert_ptr = setup_file_data;
2126 // on top invocation, create hash to mark included files (to prevent loops)
2127 if (top_recursion_level)
2128 include_filename_hash = newSetupFileHash();
2130 // mark this file as already included (to prevent including it again)
2131 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2133 while (!checkEndOfFile(file))
2135 // read next line of input file
2136 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2139 // check if line was completely read and is terminated by line break
2140 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2143 // cut trailing line break (this can be newline and/or carriage return)
2144 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2145 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2148 // copy raw input line for later use (mainly debugging output)
2149 strcpy(line_raw, line);
2151 if (read_continued_line)
2153 // append new line to existing line, if there is enough space
2154 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2155 strcat(previous_line, line_ptr);
2157 strcpy(line, previous_line); // copy storage buffer to line
2159 read_continued_line = FALSE;
2162 // if the last character is '\', continue at next line
2163 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2165 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2166 strcpy(previous_line, line); // copy line to storage buffer
2168 read_continued_line = TRUE;
2173 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2174 line_raw, line_nr, FALSE))
2179 if (strEqual(token, "include"))
2181 if (getHashEntry(include_filename_hash, value) == NULL)
2183 char *basepath = getBasePath(filename);
2184 char *basename = getBaseName(value);
2185 char *filename_include = getPath2(basepath, basename);
2187 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2191 free(filename_include);
2197 Warn("ignoring already processed file '%s'", value);
2204 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2206 getHashEntry((SetupFileHash *)setup_file_data, token);
2208 if (old_value != NULL)
2210 if (!token_already_exists_warning)
2212 Debug("setup", "---");
2213 Debug("setup", "duplicate token(s) found in config file:");
2214 Debug("setup", "- config file: '%s'", filename);
2216 token_already_exists_warning = TRUE;
2219 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2220 Debug("setup", " old value: '%s'", old_value);
2221 Debug("setup", " new value: '%s'", value);
2225 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2229 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2239 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2240 if (token_value_separator_warning)
2241 Debug("setup", "---");
2244 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2245 if (token_already_exists_warning)
2246 Debug("setup", "---");
2249 if (token_count == 0 && include_count == 0)
2250 Warn("configuration file '%s' is empty", filename);
2252 if (top_recursion_level)
2253 freeSetupFileHash(include_filename_hash);
2258 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2262 if (!(file = fopen(filename, MODE_WRITE)))
2264 Warn("cannot write configuration file '%s'", filename);
2269 BEGIN_HASH_ITERATION(hash, itr)
2271 fprintf(file, "%s\n", getFormattedSetupEntry(HASH_ITERATION_TOKEN(itr),
2272 HASH_ITERATION_VALUE(itr)));
2274 END_HASH_ITERATION(hash, itr)
2279 SetupFileList *loadSetupFileList(char *filename)
2281 SetupFileList *setup_file_list = newSetupFileList("", "");
2282 SetupFileList *first_valid_list_entry;
2284 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2286 freeSetupFileList(setup_file_list);
2291 first_valid_list_entry = setup_file_list->next;
2293 // free empty list header
2294 setup_file_list->next = NULL;
2295 freeSetupFileList(setup_file_list);
2297 return first_valid_list_entry;
2300 SetupFileHash *loadSetupFileHash(char *filename)
2302 SetupFileHash *setup_file_hash = newSetupFileHash();
2304 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2306 freeSetupFileHash(setup_file_hash);
2311 return setup_file_hash;
2315 // ============================================================================
2317 // ============================================================================
2319 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2320 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2321 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2323 // level directory info
2324 #define LEVELINFO_TOKEN_IDENTIFIER 0
2325 #define LEVELINFO_TOKEN_NAME 1
2326 #define LEVELINFO_TOKEN_NAME_SORTING 2
2327 #define LEVELINFO_TOKEN_AUTHOR 3
2328 #define LEVELINFO_TOKEN_YEAR 4
2329 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2330 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2331 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2332 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2333 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2334 #define LEVELINFO_TOKEN_TESTED_BY 10
2335 #define LEVELINFO_TOKEN_LEVELS 11
2336 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2337 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2338 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2339 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2340 #define LEVELINFO_TOKEN_READONLY 16
2341 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2342 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2343 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2344 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2345 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2346 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2347 #define LEVELINFO_TOKEN_MUSIC_SET 23
2348 #define LEVELINFO_TOKEN_FILENAME 24
2349 #define LEVELINFO_TOKEN_FILETYPE 25
2350 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2351 #define LEVELINFO_TOKEN_HANDICAP 27
2352 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2353 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2355 #define NUM_LEVELINFO_TOKENS 30
2357 static LevelDirTree ldi;
2359 static struct TokenInfo levelinfo_tokens[] =
2361 // level directory info
2362 { TYPE_STRING, &ldi.identifier, "identifier" },
2363 { TYPE_STRING, &ldi.name, "name" },
2364 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2365 { TYPE_STRING, &ldi.author, "author" },
2366 { TYPE_STRING, &ldi.year, "year" },
2367 { TYPE_STRING, &ldi.program_title, "program_title" },
2368 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2369 { TYPE_STRING, &ldi.program_company, "program_company" },
2370 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2371 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2372 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2373 { TYPE_INTEGER, &ldi.levels, "levels" },
2374 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2375 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2376 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2377 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2378 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2379 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2380 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2381 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2382 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2383 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2384 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2385 { TYPE_STRING, &ldi.music_set, "music_set" },
2386 { TYPE_STRING, &ldi.level_filename, "filename" },
2387 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2388 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2389 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2390 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2391 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2394 static struct TokenInfo artworkinfo_tokens[] =
2396 // artwork directory info
2397 { TYPE_STRING, &ldi.identifier, "identifier" },
2398 { TYPE_STRING, &ldi.subdir, "subdir" },
2399 { TYPE_STRING, &ldi.name, "name" },
2400 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2401 { TYPE_STRING, &ldi.author, "author" },
2402 { TYPE_STRING, &ldi.program_title, "program_title" },
2403 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2404 { TYPE_STRING, &ldi.program_company, "program_company" },
2405 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2406 { TYPE_STRING, &ldi.basepath, "basepath" },
2407 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2408 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2409 { TYPE_INTEGER, &ldi.color, "color" },
2410 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2415 static char *optional_tokens[] =
2418 "program_copyright",
2424 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2428 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2429 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2430 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2431 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2434 ti->node_parent = NULL;
2435 ti->node_group = NULL;
2442 ti->fullpath = NULL;
2443 ti->basepath = NULL;
2444 ti->identifier = NULL;
2445 ti->name = getStringCopy(ANONYMOUS_NAME);
2446 ti->name_sorting = NULL;
2447 ti->author = getStringCopy(ANONYMOUS_NAME);
2450 ti->program_title = NULL;
2451 ti->program_copyright = NULL;
2452 ti->program_company = NULL;
2454 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2455 ti->latest_engine = FALSE; // default: get from level
2456 ti->parent_link = FALSE;
2457 ti->in_user_dir = FALSE;
2458 ti->user_defined = FALSE;
2460 ti->class_desc = NULL;
2462 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2464 if (ti->type == TREE_TYPE_LEVEL_DIR)
2466 ti->imported_from = NULL;
2467 ti->imported_by = NULL;
2468 ti->tested_by = NULL;
2470 ti->graphics_set_ecs = NULL;
2471 ti->graphics_set_aga = NULL;
2472 ti->graphics_set = NULL;
2473 ti->sounds_set_default = NULL;
2474 ti->sounds_set_lowpass = NULL;
2475 ti->sounds_set = NULL;
2476 ti->music_set = NULL;
2477 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2478 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2479 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2481 ti->level_filename = NULL;
2482 ti->level_filetype = NULL;
2484 ti->special_flags = NULL;
2487 ti->first_level = 0;
2489 ti->level_group = FALSE;
2490 ti->handicap_level = 0;
2491 ti->readonly = TRUE;
2492 ti->handicap = TRUE;
2493 ti->skip_levels = FALSE;
2495 ti->use_emc_tiles = FALSE;
2499 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2503 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2505 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2510 // copy all values from the parent structure
2512 ti->type = parent->type;
2514 ti->node_top = parent->node_top;
2515 ti->node_parent = parent;
2516 ti->node_group = NULL;
2523 ti->fullpath = NULL;
2524 ti->basepath = NULL;
2525 ti->identifier = NULL;
2526 ti->name = getStringCopy(ANONYMOUS_NAME);
2527 ti->name_sorting = NULL;
2528 ti->author = getStringCopy(parent->author);
2529 ti->year = getStringCopy(parent->year);
2531 ti->program_title = getStringCopy(parent->program_title);
2532 ti->program_copyright = getStringCopy(parent->program_copyright);
2533 ti->program_company = getStringCopy(parent->program_company);
2535 ti->sort_priority = parent->sort_priority;
2536 ti->latest_engine = parent->latest_engine;
2537 ti->parent_link = FALSE;
2538 ti->in_user_dir = parent->in_user_dir;
2539 ti->user_defined = parent->user_defined;
2540 ti->color = parent->color;
2541 ti->class_desc = getStringCopy(parent->class_desc);
2543 ti->infotext = getStringCopy(parent->infotext);
2545 if (ti->type == TREE_TYPE_LEVEL_DIR)
2547 ti->imported_from = getStringCopy(parent->imported_from);
2548 ti->imported_by = getStringCopy(parent->imported_by);
2549 ti->tested_by = getStringCopy(parent->tested_by);
2551 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2552 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2553 ti->graphics_set = getStringCopy(parent->graphics_set);
2554 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2555 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2556 ti->sounds_set = getStringCopy(parent->sounds_set);
2557 ti->music_set = getStringCopy(parent->music_set);
2558 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2559 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2560 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2562 ti->level_filename = getStringCopy(parent->level_filename);
2563 ti->level_filetype = getStringCopy(parent->level_filetype);
2565 ti->special_flags = getStringCopy(parent->special_flags);
2567 ti->levels = parent->levels;
2568 ti->first_level = parent->first_level;
2569 ti->last_level = parent->last_level;
2570 ti->level_group = FALSE;
2571 ti->handicap_level = parent->handicap_level;
2572 ti->readonly = parent->readonly;
2573 ti->handicap = parent->handicap;
2574 ti->skip_levels = parent->skip_levels;
2576 ti->use_emc_tiles = parent->use_emc_tiles;
2580 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2582 TreeInfo *ti_copy = newTreeInfo();
2584 // copy all values from the original structure
2586 ti_copy->type = ti->type;
2588 ti_copy->node_top = ti->node_top;
2589 ti_copy->node_parent = ti->node_parent;
2590 ti_copy->node_group = ti->node_group;
2591 ti_copy->next = ti->next;
2593 ti_copy->cl_first = ti->cl_first;
2594 ti_copy->cl_cursor = ti->cl_cursor;
2596 ti_copy->subdir = getStringCopy(ti->subdir);
2597 ti_copy->fullpath = getStringCopy(ti->fullpath);
2598 ti_copy->basepath = getStringCopy(ti->basepath);
2599 ti_copy->identifier = getStringCopy(ti->identifier);
2600 ti_copy->name = getStringCopy(ti->name);
2601 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2602 ti_copy->author = getStringCopy(ti->author);
2603 ti_copy->year = getStringCopy(ti->year);
2605 ti_copy->program_title = getStringCopy(ti->program_title);
2606 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2607 ti_copy->program_company = getStringCopy(ti->program_company);
2609 ti_copy->imported_from = getStringCopy(ti->imported_from);
2610 ti_copy->imported_by = getStringCopy(ti->imported_by);
2611 ti_copy->tested_by = getStringCopy(ti->tested_by);
2613 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2614 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2615 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2616 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2617 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2618 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2619 ti_copy->music_set = getStringCopy(ti->music_set);
2620 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2621 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2622 ti_copy->music_path = getStringCopy(ti->music_path);
2624 ti_copy->level_filename = getStringCopy(ti->level_filename);
2625 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2627 ti_copy->special_flags = getStringCopy(ti->special_flags);
2629 ti_copy->levels = ti->levels;
2630 ti_copy->first_level = ti->first_level;
2631 ti_copy->last_level = ti->last_level;
2632 ti_copy->sort_priority = ti->sort_priority;
2634 ti_copy->latest_engine = ti->latest_engine;
2636 ti_copy->level_group = ti->level_group;
2637 ti_copy->parent_link = ti->parent_link;
2638 ti_copy->in_user_dir = ti->in_user_dir;
2639 ti_copy->user_defined = ti->user_defined;
2640 ti_copy->readonly = ti->readonly;
2641 ti_copy->handicap = ti->handicap;
2642 ti_copy->skip_levels = ti->skip_levels;
2644 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2646 ti_copy->color = ti->color;
2647 ti_copy->class_desc = getStringCopy(ti->class_desc);
2648 ti_copy->handicap_level = ti->handicap_level;
2650 ti_copy->infotext = getStringCopy(ti->infotext);
2655 void freeTreeInfo(TreeInfo *ti)
2660 checked_free(ti->subdir);
2661 checked_free(ti->fullpath);
2662 checked_free(ti->basepath);
2663 checked_free(ti->identifier);
2665 checked_free(ti->name);
2666 checked_free(ti->name_sorting);
2667 checked_free(ti->author);
2668 checked_free(ti->year);
2670 checked_free(ti->program_title);
2671 checked_free(ti->program_copyright);
2672 checked_free(ti->program_company);
2674 checked_free(ti->class_desc);
2676 checked_free(ti->infotext);
2678 if (ti->type == TREE_TYPE_LEVEL_DIR)
2680 checked_free(ti->imported_from);
2681 checked_free(ti->imported_by);
2682 checked_free(ti->tested_by);
2684 checked_free(ti->graphics_set_ecs);
2685 checked_free(ti->graphics_set_aga);
2686 checked_free(ti->graphics_set);
2687 checked_free(ti->sounds_set_default);
2688 checked_free(ti->sounds_set_lowpass);
2689 checked_free(ti->sounds_set);
2690 checked_free(ti->music_set);
2692 checked_free(ti->graphics_path);
2693 checked_free(ti->sounds_path);
2694 checked_free(ti->music_path);
2696 checked_free(ti->level_filename);
2697 checked_free(ti->level_filetype);
2699 checked_free(ti->special_flags);
2702 // recursively free child node
2704 freeTreeInfo(ti->node_group);
2706 // recursively free next node
2708 freeTreeInfo(ti->next);
2713 void setSetupInfo(struct TokenInfo *token_info,
2714 int token_nr, char *token_value)
2716 int token_type = token_info[token_nr].type;
2717 void *setup_value = token_info[token_nr].value;
2719 if (token_value == NULL)
2722 // set setup field to corresponding token value
2727 *(boolean *)setup_value = get_boolean_from_string(token_value);
2731 *(int *)setup_value = get_switch3_from_string(token_value);
2735 *(Key *)setup_value = getKeyFromKeyName(token_value);
2739 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2743 *(int *)setup_value = get_integer_from_string(token_value);
2747 checked_free(*(char **)setup_value);
2748 *(char **)setup_value = getStringCopy(token_value);
2752 *(int *)setup_value = get_player_nr_from_string(token_value);
2760 static int compareTreeInfoEntries(const void *object1, const void *object2)
2762 const TreeInfo *entry1 = *((TreeInfo **)object1);
2763 const TreeInfo *entry2 = *((TreeInfo **)object2);
2764 int class_sorting1 = 0, class_sorting2 = 0;
2767 if (entry1->type == TREE_TYPE_LEVEL_DIR)
2769 class_sorting1 = LEVELSORTING(entry1);
2770 class_sorting2 = LEVELSORTING(entry2);
2772 else if (entry1->type == TREE_TYPE_GRAPHICS_DIR ||
2773 entry1->type == TREE_TYPE_SOUNDS_DIR ||
2774 entry1->type == TREE_TYPE_MUSIC_DIR)
2776 class_sorting1 = ARTWORKSORTING(entry1);
2777 class_sorting2 = ARTWORKSORTING(entry2);
2780 if (entry1->parent_link || entry2->parent_link)
2781 compare_result = (entry1->parent_link ? -1 : +1);
2782 else if (entry1->sort_priority == entry2->sort_priority)
2784 char *name1 = getStringToLower(entry1->name_sorting);
2785 char *name2 = getStringToLower(entry2->name_sorting);
2787 compare_result = strcmp(name1, name2);
2792 else if (class_sorting1 == class_sorting2)
2793 compare_result = entry1->sort_priority - entry2->sort_priority;
2795 compare_result = class_sorting1 - class_sorting2;
2797 return compare_result;
2800 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2804 if (node_parent == NULL)
2807 ti_new = newTreeInfo();
2808 setTreeInfoToDefaults(ti_new, node_parent->type);
2810 ti_new->node_parent = node_parent;
2811 ti_new->parent_link = TRUE;
2813 setString(&ti_new->identifier, node_parent->identifier);
2814 setString(&ti_new->name, ".. (parent directory)");
2815 setString(&ti_new->name_sorting, ti_new->name);
2817 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2818 setString(&ti_new->fullpath, node_parent->fullpath);
2820 ti_new->sort_priority = node_parent->sort_priority;
2821 ti_new->latest_engine = node_parent->latest_engine;
2823 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2825 pushTreeInfo(&node_parent->node_group, ti_new);
2830 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2832 TreeInfo *ti_new, *ti_new2;
2834 if (node_first == NULL)
2837 ti_new = newTreeInfo();
2838 setTreeInfoToDefaults(ti_new, TREE_TYPE_LEVEL_DIR);
2840 ti_new->node_parent = NULL;
2841 ti_new->parent_link = FALSE;
2843 setString(&ti_new->identifier, node_first->identifier);
2844 setString(&ti_new->name, "level sets");
2845 setString(&ti_new->name_sorting, ti_new->name);
2847 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2848 setString(&ti_new->fullpath, ".");
2850 ti_new->sort_priority = node_first->sort_priority;;
2851 ti_new->latest_engine = node_first->latest_engine;
2853 setString(&ti_new->class_desc, "level sets");
2855 ti_new->node_group = node_first;
2856 ti_new->level_group = TRUE;
2858 ti_new2 = createParentTreeInfoNode(ti_new);
2860 setString(&ti_new2->name, ".. (main menu)");
2861 setString(&ti_new2->name_sorting, ti_new2->name);
2867 // ----------------------------------------------------------------------------
2868 // functions for handling level and custom artwork info cache
2869 // ----------------------------------------------------------------------------
2871 static void LoadArtworkInfoCache(void)
2873 InitCacheDirectory();
2875 if (artworkinfo_cache_old == NULL)
2877 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2879 // try to load artwork info hash from already existing cache file
2880 artworkinfo_cache_old = loadSetupFileHash(filename);
2882 // if no artwork info cache file was found, start with empty hash
2883 if (artworkinfo_cache_old == NULL)
2884 artworkinfo_cache_old = newSetupFileHash();
2889 if (artworkinfo_cache_new == NULL)
2890 artworkinfo_cache_new = newSetupFileHash();
2893 static void SaveArtworkInfoCache(void)
2895 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2897 InitCacheDirectory();
2899 saveSetupFileHash(artworkinfo_cache_new, filename);
2904 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2906 static char *prefix = NULL;
2908 checked_free(prefix);
2910 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
2915 // (identical to above function, but separate string buffer needed -- nasty)
2916 static char *getCacheToken(char *prefix, char *suffix)
2918 static char *token = NULL;
2920 checked_free(token);
2922 token = getStringCat2WithSeparator(prefix, suffix, ".");
2927 static char *getFileTimestampString(char *filename)
2929 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
2932 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
2934 struct stat file_status;
2936 if (timestamp_string == NULL)
2939 if (stat(filename, &file_status) != 0) // cannot stat file
2942 return (file_status.st_mtime != atoi(timestamp_string));
2945 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
2947 char *identifier = level_node->subdir;
2948 char *type_string = ARTWORK_DIRECTORY(type);
2949 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
2950 char *token_main = getCacheToken(token_prefix, "CACHED");
2951 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
2952 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
2953 TreeInfo *artwork_info = NULL;
2955 if (!use_artworkinfo_cache)
2958 if (optional_tokens_hash == NULL)
2962 // create hash from list of optional tokens (for quick access)
2963 optional_tokens_hash = newSetupFileHash();
2964 for (i = 0; optional_tokens[i] != NULL; i++)
2965 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
2972 artwork_info = newTreeInfo();
2973 setTreeInfoToDefaults(artwork_info, type);
2975 // set all structure fields according to the token/value pairs
2976 ldi = *artwork_info;
2977 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
2979 char *token_suffix = artworkinfo_tokens[i].text;
2980 char *token = getCacheToken(token_prefix, token_suffix);
2981 char *value = getHashEntry(artworkinfo_cache_old, token);
2983 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
2985 setSetupInfo(artworkinfo_tokens, i, value);
2987 // check if cache entry for this item is mandatory, but missing
2988 if (value == NULL && !optional)
2990 Warn("missing cache entry '%s'", token);
2996 *artwork_info = ldi;
3001 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3002 LEVELINFO_FILENAME);
3003 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3004 ARTWORKINFO_FILENAME(type));
3006 // check if corresponding "levelinfo.conf" file has changed
3007 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3008 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3010 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3013 // check if corresponding "<artworkinfo>.conf" file has changed
3014 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3015 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3017 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3020 checked_free(filename_levelinfo);
3021 checked_free(filename_artworkinfo);
3024 if (!cached && artwork_info != NULL)
3026 freeTreeInfo(artwork_info);
3031 return artwork_info;
3034 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3035 LevelDirTree *level_node, int type)
3037 char *identifier = level_node->subdir;
3038 char *type_string = ARTWORK_DIRECTORY(type);
3039 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3040 char *token_main = getCacheToken(token_prefix, "CACHED");
3041 boolean set_cache_timestamps = TRUE;
3044 setHashEntry(artworkinfo_cache_new, token_main, "true");
3046 if (set_cache_timestamps)
3048 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3049 LEVELINFO_FILENAME);
3050 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3051 ARTWORKINFO_FILENAME(type));
3052 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3053 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3055 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3056 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3058 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3059 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3061 checked_free(filename_levelinfo);
3062 checked_free(filename_artworkinfo);
3063 checked_free(timestamp_levelinfo);
3064 checked_free(timestamp_artworkinfo);
3067 ldi = *artwork_info;
3068 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3070 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3071 char *value = getSetupValue(artworkinfo_tokens[i].type,
3072 artworkinfo_tokens[i].value);
3074 setHashEntry(artworkinfo_cache_new, token, value);
3079 // ----------------------------------------------------------------------------
3080 // functions for loading level info and custom artwork info
3081 // ----------------------------------------------------------------------------
3083 int GetZipFileTreeType(char *zip_filename)
3085 static char *top_dir_path = NULL;
3086 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3087 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3089 GRAPHICSINFO_FILENAME,
3090 SOUNDSINFO_FILENAME,
3096 checked_free(top_dir_path);
3097 top_dir_path = NULL;
3099 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3101 checked_free(top_dir_conf_filename[j]);
3102 top_dir_conf_filename[j] = NULL;
3105 char **zip_entries = zip_list(zip_filename);
3107 // check if zip file successfully opened
3108 if (zip_entries == NULL || zip_entries[0] == NULL)
3109 return TREE_TYPE_UNDEFINED;
3111 // first zip file entry is expected to be top level directory
3112 char *top_dir = zip_entries[0];
3114 // check if valid top level directory found in zip file
3115 if (!strSuffix(top_dir, "/"))
3116 return TREE_TYPE_UNDEFINED;
3118 // get filenames of valid configuration files in top level directory
3119 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3120 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3122 int tree_type = TREE_TYPE_UNDEFINED;
3125 while (zip_entries[e] != NULL)
3127 // check if every zip file entry is below top level directory
3128 if (!strPrefix(zip_entries[e], top_dir))
3129 return TREE_TYPE_UNDEFINED;
3131 // check if this zip file entry is a valid configuration filename
3132 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3134 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3136 // only exactly one valid configuration file allowed
3137 if (tree_type != TREE_TYPE_UNDEFINED)
3138 return TREE_TYPE_UNDEFINED;
3150 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3153 static char *top_dir_path = NULL;
3154 static char *top_dir_conf_filename = NULL;
3156 checked_free(top_dir_path);
3157 checked_free(top_dir_conf_filename);
3159 top_dir_path = NULL;
3160 top_dir_conf_filename = NULL;
3162 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3163 ARTWORKINFO_FILENAME(tree_type));
3165 // check if valid configuration filename determined
3166 if (conf_basename == NULL || strEqual(conf_basename, ""))
3169 char **zip_entries = zip_list(zip_filename);
3171 // check if zip file successfully opened
3172 if (zip_entries == NULL || zip_entries[0] == NULL)
3175 // first zip file entry is expected to be top level directory
3176 char *top_dir = zip_entries[0];
3178 // check if valid top level directory found in zip file
3179 if (!strSuffix(top_dir, "/"))
3182 // get path of extracted top level directory
3183 top_dir_path = getPath2(directory, top_dir);
3185 // remove trailing directory separator from top level directory path
3186 // (required to be able to check for file and directory in next step)
3187 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3189 // check if zip file's top level directory already exists in target directory
3190 if (fileExists(top_dir_path)) // (checks for file and directory)
3193 // get filename of configuration file in top level directory
3194 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3196 boolean found_top_dir_conf_filename = FALSE;
3199 while (zip_entries[i] != NULL)
3201 // check if every zip file entry is below top level directory
3202 if (!strPrefix(zip_entries[i], top_dir))
3205 // check if this zip file entry is the configuration filename
3206 if (strEqual(zip_entries[i], top_dir_conf_filename))
3207 found_top_dir_conf_filename = TRUE;
3212 // check if valid configuration filename was found in zip file
3213 if (!found_top_dir_conf_filename)
3219 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3222 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3225 if (!zip_file_valid)
3227 Warn("zip file '%s' rejected!", zip_filename);
3232 char **zip_entries = zip_extract(zip_filename, directory);
3234 if (zip_entries == NULL)
3236 Warn("zip file '%s' could not be extracted!", zip_filename);
3241 Info("zip file '%s' successfully extracted!", zip_filename);
3243 // first zip file entry contains top level directory
3244 char *top_dir = zip_entries[0];
3246 // remove trailing directory separator from top level directory
3247 top_dir[strlen(top_dir) - 1] = '\0';
3252 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3255 DirectoryEntry *dir_entry;
3257 if ((dir = openDirectory(directory)) == NULL)
3259 // display error if directory is main "options.graphics_directory" etc.
3260 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3261 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3262 Warn("cannot read directory '%s'", directory);
3267 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3269 // skip non-zip files (and also directories with zip extension)
3270 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3273 char *zip_filename = getPath2(directory, dir_entry->basename);
3274 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3275 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3277 // check if zip file hasn't already been extracted or rejected
3278 if (!fileExists(zip_filename_extracted) &&
3279 !fileExists(zip_filename_rejected))
3281 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3283 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3284 zip_filename_rejected);
3287 // create empty file to mark zip file as extracted or rejected
3288 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3289 fclose(marker_file);
3292 free(zip_filename_extracted);
3293 free(zip_filename_rejected);
3297 closeDirectory(dir);
3300 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3301 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3303 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3304 TreeInfo *node_parent,
3305 char *level_directory,
3306 char *directory_name)
3308 char *directory_path = getPath2(level_directory, directory_name);
3309 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3310 SetupFileHash *setup_file_hash;
3311 LevelDirTree *leveldir_new = NULL;
3314 // unless debugging, silently ignore directories without "levelinfo.conf"
3315 if (!options.debug && !fileExists(filename))
3317 free(directory_path);
3323 setup_file_hash = loadSetupFileHash(filename);
3325 if (setup_file_hash == NULL)
3327 #if DEBUG_NO_CONFIG_FILE
3328 Debug("setup", "ignoring level directory '%s'", directory_path);
3331 free(directory_path);
3337 leveldir_new = newTreeInfo();
3340 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3342 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3344 leveldir_new->subdir = getStringCopy(directory_name);
3346 // set all structure fields according to the token/value pairs
3347 ldi = *leveldir_new;
3348 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3349 setSetupInfo(levelinfo_tokens, i,
3350 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3351 *leveldir_new = ldi;
3353 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3354 setString(&leveldir_new->name, leveldir_new->subdir);
3356 if (leveldir_new->identifier == NULL)
3357 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3359 if (leveldir_new->name_sorting == NULL)
3360 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3362 if (node_parent == NULL) // top level group
3364 leveldir_new->basepath = getStringCopy(level_directory);
3365 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3367 else // sub level group
3369 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3370 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3373 leveldir_new->last_level =
3374 leveldir_new->first_level + leveldir_new->levels - 1;
3376 leveldir_new->in_user_dir =
3377 (!strEqual(leveldir_new->basepath, options.level_directory));
3379 // adjust some settings if user's private level directory was detected
3380 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3381 leveldir_new->in_user_dir &&
3382 (strEqual(leveldir_new->subdir, getLoginName()) ||
3383 strEqual(leveldir_new->name, getLoginName()) ||
3384 strEqual(leveldir_new->author, getRealName())))
3386 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3387 leveldir_new->readonly = FALSE;
3390 leveldir_new->user_defined =
3391 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3393 leveldir_new->color = LEVELCOLOR(leveldir_new);
3395 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3397 leveldir_new->handicap_level = // set handicap to default value
3398 (leveldir_new->user_defined || !leveldir_new->handicap ?
3399 leveldir_new->last_level : leveldir_new->first_level);
3401 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3403 pushTreeInfo(node_first, leveldir_new);
3405 freeSetupFileHash(setup_file_hash);
3407 if (leveldir_new->level_group)
3409 // create node to link back to current level directory
3410 createParentTreeInfoNode(leveldir_new);
3412 // recursively step into sub-directory and look for more level series
3413 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3414 leveldir_new, directory_path);
3417 free(directory_path);
3423 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3424 TreeInfo *node_parent,
3425 char *level_directory)
3427 // ---------- 1st stage: process any level set zip files ----------
3429 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3431 // ---------- 2nd stage: check for level set directories ----------
3434 DirectoryEntry *dir_entry;
3435 boolean valid_entry_found = FALSE;
3437 if ((dir = openDirectory(level_directory)) == NULL)
3439 Warn("cannot read level directory '%s'", level_directory);
3444 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3446 char *directory_name = dir_entry->basename;
3447 char *directory_path = getPath2(level_directory, directory_name);
3449 // skip entries for current and parent directory
3450 if (strEqual(directory_name, ".") ||
3451 strEqual(directory_name, ".."))
3453 free(directory_path);
3458 // find out if directory entry is itself a directory
3459 if (!dir_entry->is_directory) // not a directory
3461 free(directory_path);
3466 free(directory_path);
3468 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3469 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3470 strEqual(directory_name, MUSIC_DIRECTORY))
3473 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3478 closeDirectory(dir);
3480 // special case: top level directory may directly contain "levelinfo.conf"
3481 if (node_parent == NULL && !valid_entry_found)
3483 // check if this directory directly contains a file "levelinfo.conf"
3484 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3485 level_directory, ".");
3488 if (!valid_entry_found)
3489 Warn("cannot find any valid level series in directory '%s'",
3493 boolean AdjustGraphicsForEMC(void)
3495 boolean settings_changed = FALSE;
3497 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3498 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3500 return settings_changed;
3503 boolean AdjustSoundsForEMC(void)
3505 boolean settings_changed = FALSE;
3507 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3508 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3510 return settings_changed;
3513 void LoadLevelInfo(void)
3515 InitUserLevelDirectory(getLoginName());
3517 DrawInitText("Loading level series", 120, FC_GREEN);
3519 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3520 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3522 leveldir_first = createTopTreeInfoNode(leveldir_first);
3524 /* after loading all level set information, clone the level directory tree
3525 and remove all level sets without levels (these may still contain artwork
3526 to be offered in the setup menu as "custom artwork", and are therefore
3527 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3528 leveldir_first_all = leveldir_first;
3529 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3531 AdjustGraphicsForEMC();
3532 AdjustSoundsForEMC();
3534 // before sorting, the first entries will be from the user directory
3535 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3537 if (leveldir_first == NULL)
3538 Fail("cannot find any valid level series in any directory");
3540 sortTreeInfo(&leveldir_first);
3542 #if ENABLE_UNUSED_CODE
3543 dumpTreeInfo(leveldir_first, 0);
3547 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3548 TreeInfo *node_parent,
3549 char *base_directory,
3550 char *directory_name, int type)
3552 char *directory_path = getPath2(base_directory, directory_name);
3553 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3554 SetupFileHash *setup_file_hash = NULL;
3555 TreeInfo *artwork_new = NULL;
3558 if (fileExists(filename))
3559 setup_file_hash = loadSetupFileHash(filename);
3561 if (setup_file_hash == NULL) // no config file -- look for artwork files
3564 DirectoryEntry *dir_entry;
3565 boolean valid_file_found = FALSE;
3567 if ((dir = openDirectory(directory_path)) != NULL)
3569 while ((dir_entry = readDirectory(dir)) != NULL)
3571 if (FileIsArtworkType(dir_entry->filename, type))
3573 valid_file_found = TRUE;
3579 closeDirectory(dir);
3582 if (!valid_file_found)
3584 #if DEBUG_NO_CONFIG_FILE
3585 if (!strEqual(directory_name, "."))
3586 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3589 free(directory_path);
3596 artwork_new = newTreeInfo();
3599 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3601 setTreeInfoToDefaults(artwork_new, type);
3603 artwork_new->subdir = getStringCopy(directory_name);
3605 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3607 // set all structure fields according to the token/value pairs
3609 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3610 setSetupInfo(levelinfo_tokens, i,
3611 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3614 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3615 setString(&artwork_new->name, artwork_new->subdir);
3617 if (artwork_new->identifier == NULL)
3618 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3620 if (artwork_new->name_sorting == NULL)
3621 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3624 if (node_parent == NULL) // top level group
3626 artwork_new->basepath = getStringCopy(base_directory);
3627 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3629 else // sub level group
3631 artwork_new->basepath = getStringCopy(node_parent->basepath);
3632 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3635 artwork_new->in_user_dir =
3636 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3638 // (may use ".sort_priority" from "setup_file_hash" above)
3639 artwork_new->color = ARTWORKCOLOR(artwork_new);
3641 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3643 if (setup_file_hash == NULL) // (after determining ".user_defined")
3645 if (strEqual(artwork_new->subdir, "."))
3647 if (artwork_new->user_defined)
3649 setString(&artwork_new->identifier, "private");
3650 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3654 setString(&artwork_new->identifier, "classic");
3655 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3658 // set to new values after changing ".sort_priority"
3659 artwork_new->color = ARTWORKCOLOR(artwork_new);
3661 setString(&artwork_new->class_desc,
3662 getLevelClassDescription(artwork_new));
3666 setString(&artwork_new->identifier, artwork_new->subdir);
3669 setString(&artwork_new->name, artwork_new->identifier);
3670 setString(&artwork_new->name_sorting, artwork_new->name);
3673 pushTreeInfo(node_first, artwork_new);
3675 freeSetupFileHash(setup_file_hash);
3677 free(directory_path);
3683 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3684 TreeInfo *node_parent,
3685 char *base_directory, int type)
3687 // ---------- 1st stage: process any artwork set zip files ----------
3689 ProcessZipFilesInDirectory(base_directory, type);
3691 // ---------- 2nd stage: check for artwork set directories ----------
3694 DirectoryEntry *dir_entry;
3695 boolean valid_entry_found = FALSE;
3697 if ((dir = openDirectory(base_directory)) == NULL)
3699 // display error if directory is main "options.graphics_directory" etc.
3700 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3701 Warn("cannot read directory '%s'", base_directory);
3706 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3708 char *directory_name = dir_entry->basename;
3709 char *directory_path = getPath2(base_directory, directory_name);
3711 // skip directory entries for current and parent directory
3712 if (strEqual(directory_name, ".") ||
3713 strEqual(directory_name, ".."))
3715 free(directory_path);
3720 // skip directory entries which are not a directory
3721 if (!dir_entry->is_directory) // not a directory
3723 free(directory_path);
3728 free(directory_path);
3730 // check if this directory contains artwork with or without config file
3731 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3733 directory_name, type);
3736 closeDirectory(dir);
3738 // check if this directory directly contains artwork itself
3739 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3740 base_directory, ".",
3742 if (!valid_entry_found)
3743 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3746 static TreeInfo *getDummyArtworkInfo(int type)
3748 // this is only needed when there is completely no artwork available
3749 TreeInfo *artwork_new = newTreeInfo();
3751 setTreeInfoToDefaults(artwork_new, type);
3753 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3754 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3755 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3757 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3758 setString(&artwork_new->name, UNDEFINED_FILENAME);
3759 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3764 void SetCurrentArtwork(int type)
3766 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3767 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3768 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3769 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3771 // set current artwork to artwork configured in setup menu
3772 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3774 // if not found, set current artwork to default artwork
3775 if (*current_ptr == NULL)
3776 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3778 // if not found, set current artwork to first artwork in tree
3779 if (*current_ptr == NULL)
3780 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3783 void ChangeCurrentArtworkIfNeeded(int type)
3785 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3786 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3788 if (!strEqual(current_identifier, setup_set))
3789 SetCurrentArtwork(type);
3792 void LoadArtworkInfo(void)
3794 LoadArtworkInfoCache();
3796 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3798 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3799 options.graphics_directory,
3800 TREE_TYPE_GRAPHICS_DIR);
3801 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3802 getUserGraphicsDir(),
3803 TREE_TYPE_GRAPHICS_DIR);
3805 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3806 options.sounds_directory,
3807 TREE_TYPE_SOUNDS_DIR);
3808 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3810 TREE_TYPE_SOUNDS_DIR);
3812 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3813 options.music_directory,
3814 TREE_TYPE_MUSIC_DIR);
3815 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3817 TREE_TYPE_MUSIC_DIR);
3819 if (artwork.gfx_first == NULL)
3820 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3821 if (artwork.snd_first == NULL)
3822 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3823 if (artwork.mus_first == NULL)
3824 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3826 // before sorting, the first entries will be from the user directory
3827 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3828 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3829 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3831 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3832 artwork.snd_current_identifier = artwork.snd_current->identifier;
3833 artwork.mus_current_identifier = artwork.mus_current->identifier;
3835 #if ENABLE_UNUSED_CODE
3836 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3837 artwork.gfx_current_identifier);
3838 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3839 artwork.snd_current_identifier);
3840 Debug("setup:LoadArtworkInfo", "music set == %s",
3841 artwork.mus_current_identifier);
3844 sortTreeInfo(&artwork.gfx_first);
3845 sortTreeInfo(&artwork.snd_first);
3846 sortTreeInfo(&artwork.mus_first);
3848 #if ENABLE_UNUSED_CODE
3849 dumpTreeInfo(artwork.gfx_first, 0);
3850 dumpTreeInfo(artwork.snd_first, 0);
3851 dumpTreeInfo(artwork.mus_first, 0);
3855 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
3856 LevelDirTree *level_node)
3858 int type = (*artwork_node)->type;
3860 // recursively check all level directories for artwork sub-directories
3864 // check all tree entries for artwork, but skip parent link entries
3865 if (!level_node->parent_link)
3867 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3868 boolean cached = (artwork_new != NULL);
3872 pushTreeInfo(artwork_node, artwork_new);
3876 TreeInfo *topnode_last = *artwork_node;
3877 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3878 ARTWORK_DIRECTORY(type));
3880 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3882 if (topnode_last != *artwork_node) // check for newly added node
3884 artwork_new = *artwork_node;
3886 setString(&artwork_new->identifier, level_node->subdir);
3887 setString(&artwork_new->name, level_node->name);
3888 setString(&artwork_new->name_sorting, level_node->name_sorting);
3890 artwork_new->sort_priority = level_node->sort_priority;
3891 artwork_new->color = LEVELCOLOR(artwork_new);
3897 // insert artwork info (from old cache or filesystem) into new cache
3898 if (artwork_new != NULL)
3899 setArtworkInfoCacheEntry(artwork_new, level_node, type);
3902 DrawInitText(level_node->name, 150, FC_YELLOW);
3904 if (level_node->node_group != NULL)
3905 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
3907 level_node = level_node->next;
3911 void LoadLevelArtworkInfo(void)
3913 print_timestamp_init("LoadLevelArtworkInfo");
3915 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
3917 print_timestamp_time("DrawTimeText");
3919 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first_all);
3920 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
3921 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first_all);
3922 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
3923 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first_all);
3924 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
3926 SaveArtworkInfoCache();
3928 print_timestamp_time("SaveArtworkInfoCache");
3930 // needed for reloading level artwork not known at ealier stage
3931 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
3932 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
3933 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
3935 print_timestamp_time("getTreeInfoFromIdentifier");
3937 sortTreeInfo(&artwork.gfx_first);
3938 sortTreeInfo(&artwork.snd_first);
3939 sortTreeInfo(&artwork.mus_first);
3941 print_timestamp_time("sortTreeInfo");
3943 #if ENABLE_UNUSED_CODE
3944 dumpTreeInfo(artwork.gfx_first, 0);
3945 dumpTreeInfo(artwork.snd_first, 0);
3946 dumpTreeInfo(artwork.mus_first, 0);
3949 print_timestamp_done("LoadLevelArtworkInfo");
3952 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
3953 char *tree_subdir_new, int type)
3955 if (tree_node_old == NULL)
3957 if (type == TREE_TYPE_LEVEL_DIR)
3959 // get level info tree node of personal user level set
3960 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
3962 // this may happen if "setup.internal.create_user_levelset" is FALSE
3963 // or if file "levelinfo.conf" is missing in personal user level set
3964 if (tree_node_old == NULL)
3965 tree_node_old = leveldir_first->node_group;
3969 // get artwork info tree node of first artwork set
3970 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
3974 if (tree_dir == NULL)
3975 tree_dir = TREE_USERDIR(type);
3977 if (tree_node_old == NULL ||
3979 tree_subdir_new == NULL) // should not happen
3982 int draw_deactivation_mask = GetDrawDeactivationMask();
3984 // override draw deactivation mask (temporarily disable drawing)
3985 SetDrawDeactivationMask(REDRAW_ALL);
3987 if (type == TREE_TYPE_LEVEL_DIR)
3989 // load new level set config and add it next to first user level set
3990 LoadLevelInfoFromLevelConf(&tree_node_old->next,
3991 tree_node_old->node_parent,
3992 tree_dir, tree_subdir_new);
3996 // load new artwork set config and add it next to first artwork set
3997 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
3998 tree_node_old->node_parent,
3999 tree_dir, tree_subdir_new, type);
4002 // set draw deactivation mask to previous value
4003 SetDrawDeactivationMask(draw_deactivation_mask);
4005 // get first node of level or artwork info tree
4006 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4008 // get tree info node of newly added level or artwork set
4009 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4012 if (tree_node_new == NULL) // should not happen
4015 // correct top link and parent node link of newly created tree node
4016 tree_node_new->node_top = tree_node_old->node_top;
4017 tree_node_new->node_parent = tree_node_old->node_parent;
4019 // sort tree info to adjust position of newly added tree set
4020 sortTreeInfo(tree_node_first);
4025 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4026 char *tree_subdir_new, int type)
4028 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4029 Fail("internal tree info structure corrupted -- aborting");
4032 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4034 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4037 char *getArtworkIdentifierForUserLevelSet(int type)
4039 char *classic_artwork_set = getClassicArtworkSet(type);
4041 // check for custom artwork configured in "levelinfo.conf"
4042 char *leveldir_artwork_set =
4043 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4044 boolean has_leveldir_artwork_set =
4045 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4046 classic_artwork_set));
4048 // check for custom artwork in sub-directory "graphics" etc.
4049 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4050 char *leveldir_identifier = leveldir_current->identifier;
4051 boolean has_artwork_subdir =
4052 (getTreeInfoFromIdentifier(artwork_first_node,
4053 leveldir_identifier) != NULL);
4055 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4056 has_artwork_subdir ? leveldir_identifier :
4057 classic_artwork_set);
4060 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4062 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4063 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4064 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4068 ti = getTreeInfoFromIdentifier(artwork_first_node,
4069 ARTWORK_DEFAULT_SUBDIR(type));
4071 Fail("cannot find default graphics -- should not happen");
4077 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4079 char *graphics_set =
4080 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4082 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4084 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4086 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4087 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4088 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4091 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4092 char *level_author, int num_levels)
4094 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4095 char *filename_tmp = getStringCat2(filename, ".tmp");
4097 FILE *file_tmp = NULL;
4098 char line[MAX_LINE_LEN];
4099 boolean success = FALSE;
4100 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4102 // update values in level directory tree
4104 if (level_name != NULL)
4105 setString(&leveldir->name, level_name);
4107 if (level_author != NULL)
4108 setString(&leveldir->author, level_author);
4110 if (num_levels != -1)
4111 leveldir->levels = num_levels;
4113 // update values that depend on other values
4115 setString(&leveldir->name_sorting, leveldir->name);
4117 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4119 // sort order of level sets may have changed
4120 sortTreeInfo(&leveldir_first);
4122 if ((file = fopen(filename, MODE_READ)) &&
4123 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4125 while (fgets(line, MAX_LINE_LEN, file))
4127 if (strPrefix(line, "name:") && level_name != NULL)
4128 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4129 else if (strPrefix(line, "author:") && level_author != NULL)
4130 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4131 else if (strPrefix(line, "levels:") && num_levels != -1)
4132 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4134 fputs(line, file_tmp);
4147 success = (rename(filename_tmp, filename) == 0);
4155 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4156 char *level_author, int num_levels,
4157 boolean use_artwork_set)
4159 LevelDirTree *level_info;
4164 // create user level sub-directory, if needed
4165 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4167 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4169 if (!(file = fopen(filename, MODE_WRITE)))
4171 Warn("cannot write level info file '%s'", filename);
4178 level_info = newTreeInfo();
4180 // always start with reliable default values
4181 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4183 setString(&level_info->name, level_name);
4184 setString(&level_info->author, level_author);
4185 level_info->levels = num_levels;
4186 level_info->first_level = 1;
4187 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4188 level_info->readonly = FALSE;
4190 if (use_artwork_set)
4192 level_info->graphics_set =
4193 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4194 level_info->sounds_set =
4195 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4196 level_info->music_set =
4197 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4200 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4202 fprintFileHeader(file, LEVELINFO_FILENAME);
4205 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4207 if (i == LEVELINFO_TOKEN_NAME ||
4208 i == LEVELINFO_TOKEN_AUTHOR ||
4209 i == LEVELINFO_TOKEN_LEVELS ||
4210 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4211 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4212 i == LEVELINFO_TOKEN_READONLY ||
4213 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4214 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4215 i == LEVELINFO_TOKEN_MUSIC_SET)))
4216 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4218 // just to make things nicer :)
4219 if (i == LEVELINFO_TOKEN_AUTHOR ||
4220 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4221 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4222 fprintf(file, "\n");
4225 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4229 SetFilePermissions(filename, PERMS_PRIVATE);
4231 freeTreeInfo(level_info);
4237 static void SaveUserLevelInfo(void)
4239 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4242 char *getSetupValue(int type, void *value)
4244 static char value_string[MAX_LINE_LEN];
4252 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4256 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4260 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4261 *(int *)value == FALSE ? "off" : "on"));
4265 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4268 case TYPE_YES_NO_AUTO:
4269 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4270 *(int *)value == FALSE ? "no" : "yes"));
4274 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4278 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4282 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4286 sprintf(value_string, "%d", *(int *)value);
4290 if (*(char **)value == NULL)
4293 strcpy(value_string, *(char **)value);
4297 sprintf(value_string, "player_%d", *(int *)value + 1);
4301 value_string[0] = '\0';
4305 if (type & TYPE_GHOSTED)
4306 strcpy(value_string, "n/a");
4308 return value_string;
4311 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4315 static char token_string[MAX_LINE_LEN];
4316 int token_type = token_info[token_nr].type;
4317 void *setup_value = token_info[token_nr].value;
4318 char *token_text = token_info[token_nr].text;
4319 char *value_string = getSetupValue(token_type, setup_value);
4321 // build complete token string
4322 sprintf(token_string, "%s%s", prefix, token_text);
4324 // build setup entry line
4325 line = getFormattedSetupEntry(token_string, value_string);
4327 if (token_type == TYPE_KEY_X11)
4329 Key key = *(Key *)setup_value;
4330 char *keyname = getKeyNameFromKey(key);
4332 // add comment, if useful
4333 if (!strEqual(keyname, "(undefined)") &&
4334 !strEqual(keyname, "(unknown)"))
4336 // add at least one whitespace
4338 for (i = strlen(line); i < token_comment_position; i++)
4342 strcat(line, keyname);
4349 void LoadLevelSetup_LastSeries(void)
4351 // --------------------------------------------------------------------------
4352 // ~/.<program>/levelsetup.conf
4353 // --------------------------------------------------------------------------
4355 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4356 SetupFileHash *level_setup_hash = NULL;
4358 // always start with reliable default values
4359 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4361 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4363 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4365 if (leveldir_current == NULL)
4366 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4369 if ((level_setup_hash = loadSetupFileHash(filename)))
4371 char *last_level_series =
4372 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4374 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4376 if (leveldir_current == NULL)
4377 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4379 freeSetupFileHash(level_setup_hash);
4383 Debug("setup", "using default setup values");
4389 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4391 // --------------------------------------------------------------------------
4392 // ~/.<program>/levelsetup.conf
4393 // --------------------------------------------------------------------------
4395 // check if the current level directory structure is available at this point
4396 if (leveldir_current == NULL)
4399 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4400 char *level_subdir = leveldir_current->subdir;
4403 InitUserDataDirectory();
4405 if (!(file = fopen(filename, MODE_WRITE)))
4407 Warn("cannot write setup file '%s'", filename);
4414 fprintFileHeader(file, LEVELSETUP_FILENAME);
4416 if (deactivate_last_level_series)
4417 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4419 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4424 SetFilePermissions(filename, PERMS_PRIVATE);
4429 void SaveLevelSetup_LastSeries(void)
4431 SaveLevelSetup_LastSeries_Ext(FALSE);
4434 void SaveLevelSetup_LastSeries_Deactivate(void)
4436 SaveLevelSetup_LastSeries_Ext(TRUE);
4439 static void checkSeriesInfo(void)
4441 static char *level_directory = NULL;
4444 DirectoryEntry *dir_entry;
4447 checked_free(level_directory);
4449 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4451 level_directory = getPath2((leveldir_current->in_user_dir ?
4452 getUserLevelDir(NULL) :
4453 options.level_directory),
4454 leveldir_current->fullpath);
4456 if ((dir = openDirectory(level_directory)) == NULL)
4458 Warn("cannot read level directory '%s'", level_directory);
4464 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4466 if (strlen(dir_entry->basename) > 4 &&
4467 dir_entry->basename[3] == '.' &&
4468 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4470 char levelnum_str[4];
4473 strncpy(levelnum_str, dir_entry->basename, 3);
4474 levelnum_str[3] = '\0';
4476 levelnum_value = atoi(levelnum_str);
4478 if (levelnum_value < leveldir_current->first_level)
4480 Warn("additional level %d found", levelnum_value);
4482 leveldir_current->first_level = levelnum_value;
4484 else if (levelnum_value > leveldir_current->last_level)
4486 Warn("additional level %d found", levelnum_value);
4488 leveldir_current->last_level = levelnum_value;
4494 closeDirectory(dir);
4497 void LoadLevelSetup_SeriesInfo(void)
4500 SetupFileHash *level_setup_hash = NULL;
4501 char *level_subdir = leveldir_current->subdir;
4504 // always start with reliable default values
4505 level_nr = leveldir_current->first_level;
4507 for (i = 0; i < MAX_LEVELS; i++)
4509 LevelStats_setPlayed(i, 0);
4510 LevelStats_setSolved(i, 0);
4515 // --------------------------------------------------------------------------
4516 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4517 // --------------------------------------------------------------------------
4519 level_subdir = leveldir_current->subdir;
4521 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4523 if ((level_setup_hash = loadSetupFileHash(filename)))
4527 // get last played level in this level set
4529 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4533 level_nr = atoi(token_value);
4535 if (level_nr < leveldir_current->first_level)
4536 level_nr = leveldir_current->first_level;
4537 if (level_nr > leveldir_current->last_level)
4538 level_nr = leveldir_current->last_level;
4541 // get handicap level in this level set
4543 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4547 int level_nr = atoi(token_value);
4549 if (level_nr < leveldir_current->first_level)
4550 level_nr = leveldir_current->first_level;
4551 if (level_nr > leveldir_current->last_level + 1)
4552 level_nr = leveldir_current->last_level;
4554 if (leveldir_current->user_defined || !leveldir_current->handicap)
4555 level_nr = leveldir_current->last_level;
4557 leveldir_current->handicap_level = level_nr;
4560 // get number of played and solved levels in this level set
4562 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4564 char *token = HASH_ITERATION_TOKEN(itr);
4565 char *value = HASH_ITERATION_VALUE(itr);
4567 if (strlen(token) == 3 &&
4568 token[0] >= '0' && token[0] <= '9' &&
4569 token[1] >= '0' && token[1] <= '9' &&
4570 token[2] >= '0' && token[2] <= '9')
4572 int level_nr = atoi(token);
4575 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4577 value = strchr(value, ' ');
4580 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4583 END_HASH_ITERATION(hash, itr)
4585 freeSetupFileHash(level_setup_hash);
4589 Debug("setup", "using default setup values");
4595 void SaveLevelSetup_SeriesInfo(void)
4598 char *level_subdir = leveldir_current->subdir;
4599 char *level_nr_str = int2str(level_nr, 0);
4600 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4604 // --------------------------------------------------------------------------
4605 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4606 // --------------------------------------------------------------------------
4608 InitLevelSetupDirectory(level_subdir);
4610 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4612 if (!(file = fopen(filename, MODE_WRITE)))
4614 Warn("cannot write setup file '%s'", filename);
4621 fprintFileHeader(file, LEVELSETUP_FILENAME);
4623 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4625 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4626 handicap_level_str));
4628 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4631 if (LevelStats_getPlayed(i) > 0 ||
4632 LevelStats_getSolved(i) > 0)
4637 sprintf(token, "%03d", i);
4638 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4640 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4646 SetFilePermissions(filename, PERMS_PRIVATE);
4651 int LevelStats_getPlayed(int nr)
4653 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4656 int LevelStats_getSolved(int nr)
4658 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4661 void LevelStats_setPlayed(int nr, int value)
4663 if (nr >= 0 && nr < MAX_LEVELS)
4664 level_stats[nr].played = value;
4667 void LevelStats_setSolved(int nr, int value)
4669 if (nr >= 0 && nr < MAX_LEVELS)
4670 level_stats[nr].solved = value;
4673 void LevelStats_incPlayed(int nr)
4675 if (nr >= 0 && nr < MAX_LEVELS)
4676 level_stats[nr].played++;
4679 void LevelStats_incSolved(int nr)
4681 if (nr >= 0 && nr < MAX_LEVELS)
4682 level_stats[nr].solved++;