1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define MAX_COOKIE_LEN 256
52 #define TREE_NODE_TYPE_DEFAULT 0
53 #define TREE_NODE_TYPE_PARENT 1
54 #define TREE_NODE_TYPE_GROUP 2
55 #define TREE_NODE_TYPE_COPY 3
57 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
58 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
59 ti->is_copy ? TREE_NODE_TYPE_COPY : \
60 TREE_NODE_TYPE_DEFAULT)
63 static void setTreeInfoToDefaults(TreeInfo *, int);
64 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
65 static int compareTreeInfoEntries(const void *, const void *);
67 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
68 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
70 static SetupFileHash *artworkinfo_cache_old = NULL;
71 static SetupFileHash *artworkinfo_cache_new = NULL;
72 static SetupFileHash *optional_tokens_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static char *getLevelClassDescription(TreeInfo *ti)
83 int position = ti->sort_priority / 100;
85 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
86 return levelclass_desc[position];
88 return "Unknown Level Class";
91 static char *getScoreDir(char *level_subdir)
93 static char *score_dir = NULL;
94 static char *score_level_dir = NULL;
95 char *score_subdir = SCORES_DIRECTORY;
97 if (score_dir == NULL)
99 if (program.global_scores)
100 score_dir = getPath2(getCommonDataDir(), score_subdir);
102 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
105 if (level_subdir != NULL)
107 checked_free(score_level_dir);
109 score_level_dir = getPath2(score_dir, level_subdir);
111 return score_level_dir;
117 static char *getUserSubdir(int nr)
119 static char user_subdir[16] = { 0 };
121 sprintf(user_subdir, "%03d", nr);
126 static char *getUserDir(int nr)
128 static char *user_dir = NULL;
129 char *main_data_dir = getMainUserGameDataDir();
130 char *users_subdir = USERS_DIRECTORY;
131 char *user_subdir = getUserSubdir(nr);
133 checked_free(user_dir);
136 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
138 user_dir = getPath2(main_data_dir, users_subdir);
143 static char *getLevelSetupDir(char *level_subdir)
145 static char *levelsetup_dir = NULL;
146 char *data_dir = getUserGameDataDir();
147 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
149 checked_free(levelsetup_dir);
151 if (level_subdir != NULL)
152 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
154 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
156 return levelsetup_dir;
159 static char *getCacheDir(void)
161 static char *cache_dir = NULL;
163 if (cache_dir == NULL)
164 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
169 static char *getNetworkDir(void)
171 static char *network_dir = NULL;
173 if (network_dir == NULL)
174 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
179 char *getLevelDirFromTreeInfo(TreeInfo *node)
181 static char *level_dir = NULL;
184 return options.level_directory;
186 checked_free(level_dir);
188 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
189 options.level_directory), node->fullpath);
194 char *getUserLevelDir(char *level_subdir)
196 static char *userlevel_dir = NULL;
197 char *data_dir = getMainUserGameDataDir();
198 char *userlevel_subdir = LEVELS_DIRECTORY;
200 checked_free(userlevel_dir);
202 if (level_subdir != NULL)
203 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
205 userlevel_dir = getPath2(data_dir, userlevel_subdir);
207 return userlevel_dir;
210 char *getNetworkLevelDir(char *level_subdir)
212 static char *network_level_dir = NULL;
213 char *data_dir = getNetworkDir();
214 char *networklevel_subdir = LEVELS_DIRECTORY;
216 checked_free(network_level_dir);
218 if (level_subdir != NULL)
219 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
221 network_level_dir = getPath2(data_dir, networklevel_subdir);
223 return network_level_dir;
226 char *getCurrentLevelDir(void)
228 return getLevelDirFromTreeInfo(leveldir_current);
231 char *getNewUserLevelSubdir(void)
233 static char *new_level_subdir = NULL;
234 char *subdir_prefix = getLoginName();
235 char subdir_suffix[10];
236 int max_suffix_number = 1000;
239 while (++i < max_suffix_number)
241 sprintf(subdir_suffix, "_%d", i);
243 checked_free(new_level_subdir);
244 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
246 if (!directoryExists(getUserLevelDir(new_level_subdir)))
250 return new_level_subdir;
253 static char *getTapeDir(char *level_subdir)
255 static char *tape_dir = NULL;
256 char *data_dir = getUserGameDataDir();
257 char *tape_subdir = TAPES_DIRECTORY;
259 checked_free(tape_dir);
261 if (level_subdir != NULL)
262 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
264 tape_dir = getPath2(data_dir, tape_subdir);
269 static char *getSolutionTapeDir(void)
271 static char *tape_dir = NULL;
272 char *data_dir = getCurrentLevelDir();
273 char *tape_subdir = TAPES_DIRECTORY;
275 checked_free(tape_dir);
277 tape_dir = getPath2(data_dir, tape_subdir);
282 static char *getDefaultGraphicsDir(char *graphics_subdir)
284 static char *graphics_dir = NULL;
286 if (graphics_subdir == NULL)
287 return options.graphics_directory;
289 checked_free(graphics_dir);
291 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
296 static char *getDefaultSoundsDir(char *sounds_subdir)
298 static char *sounds_dir = NULL;
300 if (sounds_subdir == NULL)
301 return options.sounds_directory;
303 checked_free(sounds_dir);
305 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
310 static char *getDefaultMusicDir(char *music_subdir)
312 static char *music_dir = NULL;
314 if (music_subdir == NULL)
315 return options.music_directory;
317 checked_free(music_dir);
319 music_dir = getPath2(options.music_directory, music_subdir);
324 static char *getClassicArtworkSet(int type)
326 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
327 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
328 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
331 static char *getClassicArtworkDir(int type)
333 return (type == TREE_TYPE_GRAPHICS_DIR ?
334 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
335 type == TREE_TYPE_SOUNDS_DIR ?
336 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
337 type == TREE_TYPE_MUSIC_DIR ?
338 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
341 char *getUserGraphicsDir(void)
343 static char *usergraphics_dir = NULL;
345 if (usergraphics_dir == NULL)
346 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
348 return usergraphics_dir;
351 char *getUserSoundsDir(void)
353 static char *usersounds_dir = NULL;
355 if (usersounds_dir == NULL)
356 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
358 return usersounds_dir;
361 char *getUserMusicDir(void)
363 static char *usermusic_dir = NULL;
365 if (usermusic_dir == NULL)
366 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
368 return usermusic_dir;
371 static char *getSetupArtworkDir(TreeInfo *ti)
373 static char *artwork_dir = NULL;
378 checked_free(artwork_dir);
380 artwork_dir = getPath2(ti->basepath, ti->fullpath);
385 char *setLevelArtworkDir(TreeInfo *ti)
387 char **artwork_path_ptr, **artwork_set_ptr;
388 TreeInfo *level_artwork;
390 if (ti == NULL || leveldir_current == NULL)
393 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
394 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
396 checked_free(*artwork_path_ptr);
398 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
400 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
405 No (or non-existing) artwork configured in "levelinfo.conf". This would
406 normally result in using the artwork configured in the setup menu. But
407 if an artwork subdirectory exists (which might contain custom artwork
408 or an artwork configuration file), this level artwork must be treated
409 as relative to the default "classic" artwork, not to the artwork that
410 is currently configured in the setup menu.
412 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
413 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
414 the real "classic" artwork from the original R'n'D (like "gfx_classic").
417 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
419 checked_free(*artwork_set_ptr);
421 if (directoryExists(dir))
423 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
424 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
428 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
429 *artwork_set_ptr = NULL;
435 return *artwork_set_ptr;
438 static char *getLevelArtworkSet(int type)
440 if (leveldir_current == NULL)
443 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
446 static char *getLevelArtworkDir(int type)
448 if (leveldir_current == NULL)
449 return UNDEFINED_FILENAME;
451 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
454 char *getProgramMainDataPath(char *command_filename, char *base_path)
456 // check if the program's main data base directory is configured
457 if (!strEqual(base_path, "."))
458 return getStringCopy(base_path);
460 /* if the program is configured to start from current directory (default),
461 determine program package directory from program binary (some versions
462 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
463 set the current working directory to the program package directory) */
464 char *main_data_path = getBasePath(command_filename);
466 #if defined(PLATFORM_MACOSX)
467 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
469 char *main_data_path_old = main_data_path;
471 // cut relative path to Mac OS X application binary directory from path
472 main_data_path[strlen(main_data_path) -
473 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
475 // cut trailing path separator from path (but not if path is root directory)
476 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
477 main_data_path[strlen(main_data_path) - 1] = '\0';
479 // replace empty path with current directory
480 if (strEqual(main_data_path, ""))
481 main_data_path = ".";
483 // add relative path to Mac OS X application resources directory to path
484 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
486 free(main_data_path_old);
490 return main_data_path;
493 char *getProgramConfigFilename(char *command_filename)
495 static char *config_filename_1 = NULL;
496 static char *config_filename_2 = NULL;
497 static char *config_filename_3 = NULL;
498 static boolean initialized = FALSE;
502 char *command_filename_1 = getStringCopy(command_filename);
504 // strip trailing executable suffix from command filename
505 if (strSuffix(command_filename_1, ".exe"))
506 command_filename_1[strlen(command_filename_1) - 4] = '\0';
508 char *ro_base_path = getProgramMainDataPath(command_filename, RO_BASE_PATH);
509 char *conf_directory = getPath2(ro_base_path, CONF_DIRECTORY);
511 char *command_basepath = getBasePath(command_filename);
512 char *command_basename = getBaseNameNoSuffix(command_filename);
513 char *command_filename_2 = getPath2(command_basepath, command_basename);
515 config_filename_1 = getStringCat2(command_filename_1, ".conf");
516 config_filename_2 = getStringCat2(command_filename_2, ".conf");
517 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
519 checked_free(ro_base_path);
520 checked_free(conf_directory);
522 checked_free(command_basepath);
523 checked_free(command_basename);
525 checked_free(command_filename_1);
526 checked_free(command_filename_2);
531 // 1st try: look for config file that exactly matches the binary filename
532 if (fileExists(config_filename_1))
533 return config_filename_1;
535 // 2nd try: look for config file that matches binary filename without suffix
536 if (fileExists(config_filename_2))
537 return config_filename_2;
539 // 3rd try: return setup config filename in global program config directory
540 return config_filename_3;
543 char *getTapeFilename(int nr)
545 static char *filename = NULL;
546 char basename[MAX_FILENAME_LEN];
548 checked_free(filename);
550 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
551 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
556 char *getSolutionTapeFilename(int nr)
558 static char *filename = NULL;
559 char basename[MAX_FILENAME_LEN];
561 checked_free(filename);
563 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
564 filename = getPath2(getSolutionTapeDir(), basename);
566 if (!fileExists(filename))
568 static char *filename_sln = NULL;
570 checked_free(filename_sln);
572 sprintf(basename, "%03d.sln", nr);
573 filename_sln = getPath2(getSolutionTapeDir(), basename);
575 if (fileExists(filename_sln))
582 char *getScoreFilename(int nr)
584 static char *filename = NULL;
585 char basename[MAX_FILENAME_LEN];
587 checked_free(filename);
589 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
591 // used instead of "leveldir_current->subdir" (for network games)
592 filename = getPath2(getScoreDir(levelset.identifier), basename);
597 char *getSetupFilename(void)
599 static char *filename = NULL;
601 checked_free(filename);
603 filename = getPath2(getSetupDir(), SETUP_FILENAME);
608 char *getDefaultSetupFilename(void)
610 return program.config_filename;
613 char *getEditorSetupFilename(void)
615 static char *filename = NULL;
617 checked_free(filename);
618 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
620 if (fileExists(filename))
623 checked_free(filename);
624 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
629 char *getHelpAnimFilename(void)
631 static char *filename = NULL;
633 checked_free(filename);
635 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
640 char *getHelpTextFilename(void)
642 static char *filename = NULL;
644 checked_free(filename);
646 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
651 char *getLevelSetInfoFilename(void)
653 static char *filename = NULL;
668 for (i = 0; basenames[i] != NULL; i++)
670 checked_free(filename);
671 filename = getPath2(getCurrentLevelDir(), basenames[i]);
673 if (fileExists(filename))
680 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
682 static char basename[32];
684 sprintf(basename, "%s_%d.txt",
685 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
690 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
692 static char *filename = NULL;
694 boolean skip_setup_artwork = FALSE;
696 checked_free(filename);
698 basename = getLevelSetTitleMessageBasename(nr, initial);
700 if (!gfx.override_level_graphics)
702 // 1st try: look for special artwork in current level series directory
703 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
704 if (fileExists(filename))
709 // 2nd try: look for message file in current level set directory
710 filename = getPath2(getCurrentLevelDir(), basename);
711 if (fileExists(filename))
716 // check if there is special artwork configured in level series config
717 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
719 // 3rd try: look for special artwork configured in level series config
720 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
721 if (fileExists(filename))
726 // take missing artwork configured in level set config from default
727 skip_setup_artwork = TRUE;
731 if (!skip_setup_artwork)
733 // 4th try: look for special artwork in configured artwork directory
734 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
735 if (fileExists(filename))
741 // 5th try: look for default artwork in new default artwork directory
742 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
743 if (fileExists(filename))
748 // 6th try: look for default artwork in old default artwork directory
749 filename = getPath2(options.graphics_directory, basename);
750 if (fileExists(filename))
753 return NULL; // cannot find specified artwork file anywhere
756 static char *getCorrectedArtworkBasename(char *basename)
761 char *getCustomImageFilename(char *basename)
763 static char *filename = NULL;
764 boolean skip_setup_artwork = FALSE;
766 checked_free(filename);
768 basename = getCorrectedArtworkBasename(basename);
770 if (!gfx.override_level_graphics)
772 // 1st try: look for special artwork in current level series directory
773 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
774 if (fileExists(filename))
779 // check if there is special artwork configured in level series config
780 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
782 // 2nd try: look for special artwork configured in level series config
783 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
784 if (fileExists(filename))
789 // take missing artwork configured in level set config from default
790 skip_setup_artwork = TRUE;
794 if (!skip_setup_artwork)
796 // 3rd try: look for special artwork in configured artwork directory
797 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
798 if (fileExists(filename))
804 // 4th try: look for default artwork in new default artwork directory
805 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
806 if (fileExists(filename))
811 // 5th try: look for default artwork in old default artwork directory
812 filename = getImg2(options.graphics_directory, basename);
813 if (fileExists(filename))
816 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
820 Warn("cannot find artwork file '%s' (using fallback)", basename);
822 // 6th try: look for fallback artwork in old default artwork directory
823 // (needed to prevent errors when trying to access unused artwork files)
824 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
825 if (fileExists(filename))
829 return NULL; // cannot find specified artwork file anywhere
832 char *getCustomSoundFilename(char *basename)
834 static char *filename = NULL;
835 boolean skip_setup_artwork = FALSE;
837 checked_free(filename);
839 basename = getCorrectedArtworkBasename(basename);
841 if (!gfx.override_level_sounds)
843 // 1st try: look for special artwork in current level series directory
844 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
845 if (fileExists(filename))
850 // check if there is special artwork configured in level series config
851 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
853 // 2nd try: look for special artwork configured in level series config
854 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
855 if (fileExists(filename))
860 // take missing artwork configured in level set config from default
861 skip_setup_artwork = TRUE;
865 if (!skip_setup_artwork)
867 // 3rd try: look for special artwork in configured artwork directory
868 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
869 if (fileExists(filename))
875 // 4th try: look for default artwork in new default artwork directory
876 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
877 if (fileExists(filename))
882 // 5th try: look for default artwork in old default artwork directory
883 filename = getPath2(options.sounds_directory, basename);
884 if (fileExists(filename))
887 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
891 Warn("cannot find artwork file '%s' (using fallback)", basename);
893 // 6th try: look for fallback artwork in old default artwork directory
894 // (needed to prevent errors when trying to access unused artwork files)
895 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
896 if (fileExists(filename))
900 return NULL; // cannot find specified artwork file anywhere
903 char *getCustomMusicFilename(char *basename)
905 static char *filename = NULL;
906 boolean skip_setup_artwork = FALSE;
908 checked_free(filename);
910 basename = getCorrectedArtworkBasename(basename);
912 if (!gfx.override_level_music)
914 // 1st try: look for special artwork in current level series directory
915 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
916 if (fileExists(filename))
921 // check if there is special artwork configured in level series config
922 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
924 // 2nd try: look for special artwork configured in level series config
925 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
926 if (fileExists(filename))
931 // take missing artwork configured in level set config from default
932 skip_setup_artwork = TRUE;
936 if (!skip_setup_artwork)
938 // 3rd try: look for special artwork in configured artwork directory
939 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
940 if (fileExists(filename))
946 // 4th try: look for default artwork in new default artwork directory
947 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
948 if (fileExists(filename))
953 // 5th try: look for default artwork in old default artwork directory
954 filename = getPath2(options.music_directory, basename);
955 if (fileExists(filename))
958 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
962 Warn("cannot find artwork file '%s' (using fallback)", basename);
964 // 6th try: look for fallback artwork in old default artwork directory
965 // (needed to prevent errors when trying to access unused artwork files)
966 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
967 if (fileExists(filename))
971 return NULL; // cannot find specified artwork file anywhere
974 char *getCustomArtworkFilename(char *basename, int type)
976 if (type == ARTWORK_TYPE_GRAPHICS)
977 return getCustomImageFilename(basename);
978 else if (type == ARTWORK_TYPE_SOUNDS)
979 return getCustomSoundFilename(basename);
980 else if (type == ARTWORK_TYPE_MUSIC)
981 return getCustomMusicFilename(basename);
983 return UNDEFINED_FILENAME;
986 char *getCustomArtworkConfigFilename(int type)
988 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
991 char *getCustomArtworkLevelConfigFilename(int type)
993 static char *filename = NULL;
995 checked_free(filename);
997 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1002 char *getCustomMusicDirectory(void)
1004 static char *directory = NULL;
1005 boolean skip_setup_artwork = FALSE;
1007 checked_free(directory);
1009 if (!gfx.override_level_music)
1011 // 1st try: look for special artwork in current level series directory
1012 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1013 if (directoryExists(directory))
1018 // check if there is special artwork configured in level series config
1019 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1021 // 2nd try: look for special artwork configured in level series config
1022 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1023 if (directoryExists(directory))
1028 // take missing artwork configured in level set config from default
1029 skip_setup_artwork = TRUE;
1033 if (!skip_setup_artwork)
1035 // 3rd try: look for special artwork in configured artwork directory
1036 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1037 if (directoryExists(directory))
1043 // 4th try: look for default artwork in new default artwork directory
1044 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1045 if (directoryExists(directory))
1050 // 5th try: look for default artwork in old default artwork directory
1051 directory = getStringCopy(options.music_directory);
1052 if (directoryExists(directory))
1055 return NULL; // cannot find specified artwork file anywhere
1058 void InitTapeDirectory(char *level_subdir)
1060 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1061 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1062 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1065 void InitScoreDirectory(char *level_subdir)
1067 int permissions = (program.global_scores ? PERMS_PUBLIC : PERMS_PRIVATE);
1069 if (program.global_scores)
1070 createDirectory(getCommonDataDir(), "common data", permissions);
1072 createDirectory(getMainUserGameDataDir(), "main user data", permissions);
1074 createDirectory(getScoreDir(NULL), "main score", permissions);
1075 createDirectory(getScoreDir(level_subdir), "level score", permissions);
1078 static void SaveUserLevelInfo(void);
1080 void InitUserLevelDirectory(char *level_subdir)
1082 if (!directoryExists(getUserLevelDir(level_subdir)))
1084 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1085 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1086 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1088 if (setup.internal.create_user_levelset)
1089 SaveUserLevelInfo();
1093 void InitNetworkLevelDirectory(char *level_subdir)
1095 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1097 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1098 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1099 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1100 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1104 void InitLevelSetupDirectory(char *level_subdir)
1106 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1107 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1108 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1111 static void InitCacheDirectory(void)
1113 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1114 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1118 // ----------------------------------------------------------------------------
1119 // some functions to handle lists of level and artwork directories
1120 // ----------------------------------------------------------------------------
1122 TreeInfo *newTreeInfo(void)
1124 return checked_calloc(sizeof(TreeInfo));
1127 TreeInfo *newTreeInfo_setDefaults(int type)
1129 TreeInfo *ti = newTreeInfo();
1131 setTreeInfoToDefaults(ti, type);
1136 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1138 node_new->next = *node_first;
1139 *node_first = node_new;
1142 void removeTreeInfo(TreeInfo **node_first)
1144 TreeInfo *node_old = *node_first;
1146 *node_first = node_old->next;
1147 node_old->next = NULL;
1149 freeTreeInfo(node_old);
1152 int numTreeInfo(TreeInfo *node)
1165 boolean validLevelSeries(TreeInfo *node)
1167 // in a number of cases, tree node is no valid level set
1168 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1174 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1176 if (validLevelSeries(node))
1178 else if (node->is_copy)
1179 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1181 return getFirstValidTreeInfoEntry(default_node);
1184 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1189 if (node->node_group) // enter level group (step down into tree)
1190 return getFirstValidTreeInfoEntry(node->node_group);
1191 else if (node->parent_link) // skip start entry of level group
1193 if (node->next) // get first real level series entry
1194 return getFirstValidTreeInfoEntry(node->next);
1195 else // leave empty level group and go on
1196 return getFirstValidTreeInfoEntry(node->node_parent->next);
1198 else // this seems to be a regular level series
1202 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1207 if (node->node_parent == NULL) // top level group
1208 return *node->node_top;
1209 else // sub level group
1210 return node->node_parent->node_group;
1213 int numTreeInfoInGroup(TreeInfo *node)
1215 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1218 int getPosFromTreeInfo(TreeInfo *node)
1220 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1225 if (node_cmp == node)
1229 node_cmp = node_cmp->next;
1235 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1237 TreeInfo *node_default = node;
1249 return node_default;
1252 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1253 int node_type_wanted)
1255 if (identifier == NULL)
1260 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1261 strEqual(identifier, node->identifier))
1264 if (node->node_group)
1266 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1279 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1281 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1284 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1285 TreeInfo *node, boolean skip_sets_without_levels)
1292 if (!node->parent_link && !node->level_group &&
1293 skip_sets_without_levels && node->levels == 0)
1294 return cloneTreeNode(node_top, node_parent, node->next,
1295 skip_sets_without_levels);
1297 node_new = getTreeInfoCopy(node); // copy complete node
1299 node_new->node_top = node_top; // correct top node link
1300 node_new->node_parent = node_parent; // correct parent node link
1302 if (node->level_group)
1303 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1304 skip_sets_without_levels);
1306 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1307 skip_sets_without_levels);
1312 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1314 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1316 *ti_new = ti_cloned;
1319 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1321 boolean settings_changed = FALSE;
1325 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1326 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1327 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1328 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1329 char *graphics_set = NULL;
1331 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1332 graphics_set = node->graphics_set_ecs;
1334 if (node->graphics_set_aga && (want_aga || has_only_aga))
1335 graphics_set = node->graphics_set_aga;
1337 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1339 setString(&node->graphics_set, graphics_set);
1340 settings_changed = TRUE;
1343 if (node->node_group != NULL)
1344 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1349 return settings_changed;
1352 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1354 boolean settings_changed = FALSE;
1358 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1359 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1360 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1361 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1362 char *sounds_set = NULL;
1364 if (node->sounds_set_default && (want_default || has_only_default))
1365 sounds_set = node->sounds_set_default;
1367 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1368 sounds_set = node->sounds_set_lowpass;
1370 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1372 setString(&node->sounds_set, sounds_set);
1373 settings_changed = TRUE;
1376 if (node->node_group != NULL)
1377 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1382 return settings_changed;
1385 void dumpTreeInfo(TreeInfo *node, int depth)
1387 char bullet_list[] = { '-', '*', 'o' };
1391 Debug("tree", "Dumping TreeInfo:");
1395 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1397 for (i = 0; i < depth * 2; i++)
1398 DebugContinued("", " ");
1400 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1401 bullet, node->name, node->identifier,
1402 (node->node_parent ? node->node_parent->identifier : "-"),
1403 (node->node_group ? "[GROUP]" : ""));
1406 // use for dumping artwork info tree
1407 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1408 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1411 if (node->node_group != NULL)
1412 dumpTreeInfo(node->node_group, depth + 1);
1418 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1419 int (*compare_function)(const void *,
1422 int num_nodes = numTreeInfo(*node_first);
1423 TreeInfo **sort_array;
1424 TreeInfo *node = *node_first;
1430 // allocate array for sorting structure pointers
1431 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1433 // writing structure pointers to sorting array
1434 while (i < num_nodes && node) // double boundary check...
1436 sort_array[i] = node;
1442 // sorting the structure pointers in the sorting array
1443 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1446 // update the linkage of list elements with the sorted node array
1447 for (i = 0; i < num_nodes - 1; i++)
1448 sort_array[i]->next = sort_array[i + 1];
1449 sort_array[num_nodes - 1]->next = NULL;
1451 // update the linkage of the main list anchor pointer
1452 *node_first = sort_array[0];
1456 // now recursively sort the level group structures
1460 if (node->node_group != NULL)
1461 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1467 void sortTreeInfo(TreeInfo **node_first)
1469 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1473 // ============================================================================
1474 // some stuff from "files.c"
1475 // ============================================================================
1477 #if defined(PLATFORM_WIN32)
1479 #define S_IRGRP S_IRUSR
1482 #define S_IROTH S_IRUSR
1485 #define S_IWGRP S_IWUSR
1488 #define S_IWOTH S_IWUSR
1491 #define S_IXGRP S_IXUSR
1494 #define S_IXOTH S_IXUSR
1497 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1502 #endif // PLATFORM_WIN32
1504 // file permissions for newly written files
1505 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1506 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1507 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1509 #define MODE_W_PRIVATE (S_IWUSR)
1510 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1511 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1513 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1514 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1515 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1517 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1518 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1519 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1522 char *getHomeDir(void)
1524 static char *dir = NULL;
1526 #if defined(PLATFORM_WIN32)
1529 dir = checked_malloc(MAX_PATH + 1);
1531 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1534 #elif defined(PLATFORM_EMSCRIPTEN)
1535 dir = "/persistent";
1536 #elif defined(PLATFORM_UNIX)
1539 if ((dir = getenv("HOME")) == NULL)
1541 dir = getUnixHomeDir();
1544 dir = getStringCopy(dir);
1556 char *getCommonDataDir(void)
1558 static char *common_data_dir = NULL;
1560 #if defined(PLATFORM_WIN32)
1561 if (common_data_dir == NULL)
1563 char *dir = checked_malloc(MAX_PATH + 1);
1565 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
1566 && !strEqual(dir, "")) // empty for Windows 95/98
1567 common_data_dir = getPath2(dir, program.userdata_subdir);
1569 common_data_dir = options.rw_base_directory;
1572 if (common_data_dir == NULL)
1573 common_data_dir = options.rw_base_directory;
1576 return common_data_dir;
1579 char *getPersonalDataDir(void)
1581 static char *personal_data_dir = NULL;
1583 #if defined(PLATFORM_MACOSX)
1584 if (personal_data_dir == NULL)
1585 personal_data_dir = getPath2(getHomeDir(), "Documents");
1587 if (personal_data_dir == NULL)
1588 personal_data_dir = getHomeDir();
1591 return personal_data_dir;
1594 char *getMainUserGameDataDir(void)
1596 static char *main_user_data_dir = NULL;
1598 #if defined(PLATFORM_ANDROID)
1599 if (main_user_data_dir == NULL)
1600 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1601 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1602 SDL_AndroidGetExternalStoragePath() :
1603 SDL_AndroidGetInternalStoragePath());
1605 if (main_user_data_dir == NULL)
1606 main_user_data_dir = getPath2(getPersonalDataDir(),
1607 program.userdata_subdir);
1610 return main_user_data_dir;
1613 char *getUserGameDataDir(void)
1616 return getMainUserGameDataDir();
1618 return getUserDir(user.nr);
1621 char *getSetupDir(void)
1623 return getUserGameDataDir();
1626 static mode_t posix_umask(mode_t mask)
1628 #if defined(PLATFORM_UNIX)
1635 static int posix_mkdir(const char *pathname, mode_t mode)
1637 #if defined(PLATFORM_WIN32)
1638 return mkdir(pathname);
1640 return mkdir(pathname, mode);
1644 static boolean posix_process_running_setgid(void)
1646 #if defined(PLATFORM_UNIX)
1647 return (getgid() != getegid());
1653 void createDirectory(char *dir, char *text, int permission_class)
1655 if (directoryExists(dir))
1658 // leave "other" permissions in umask untouched, but ensure group parts
1659 // of USERDATA_DIR_MODE are not masked
1660 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1661 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1662 mode_t last_umask = posix_umask(0);
1663 mode_t group_umask = ~(dir_mode & S_IRWXG);
1664 int running_setgid = posix_process_running_setgid();
1666 if (permission_class == PERMS_PUBLIC)
1668 // if we're setgid, protect files against "other"
1669 // else keep umask(0) to make the dir world-writable
1672 posix_umask(last_umask & group_umask);
1674 dir_mode = DIR_PERMS_PUBLIC_ALL;
1677 if (posix_mkdir(dir, dir_mode) != 0)
1678 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1680 if (permission_class == PERMS_PUBLIC && !running_setgid)
1681 chmod(dir, dir_mode);
1683 posix_umask(last_umask); // restore previous umask
1686 void InitMainUserDataDirectory(void)
1688 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1691 void InitUserDataDirectory(void)
1693 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1697 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1698 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1702 void SetFilePermissions(char *filename, int permission_class)
1704 int running_setgid = posix_process_running_setgid();
1705 int perms = (permission_class == PERMS_PRIVATE ?
1706 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1708 if (permission_class == PERMS_PUBLIC && !running_setgid)
1709 perms = FILE_PERMS_PUBLIC_ALL;
1711 chmod(filename, perms);
1714 char *getCookie(char *file_type)
1716 static char cookie[MAX_COOKIE_LEN + 1];
1718 if (strlen(program.cookie_prefix) + 1 +
1719 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1720 return "[COOKIE ERROR]"; // should never happen
1722 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1723 program.cookie_prefix, file_type,
1724 program.version_super, program.version_major);
1729 void fprintFileHeader(FILE *file, char *basename)
1731 char *prefix = "# ";
1734 fprintf_line_with_prefix(file, prefix, sep1, 77);
1735 fprintf(file, "%s%s\n", prefix, basename);
1736 fprintf_line_with_prefix(file, prefix, sep1, 77);
1737 fprintf(file, "\n");
1740 int getFileVersionFromCookieString(const char *cookie)
1742 const char *ptr_cookie1, *ptr_cookie2;
1743 const char *pattern1 = "_FILE_VERSION_";
1744 const char *pattern2 = "?.?";
1745 const int len_cookie = strlen(cookie);
1746 const int len_pattern1 = strlen(pattern1);
1747 const int len_pattern2 = strlen(pattern2);
1748 const int len_pattern = len_pattern1 + len_pattern2;
1749 int version_super, version_major;
1751 if (len_cookie <= len_pattern)
1754 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1755 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1757 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1760 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1761 ptr_cookie2[1] != '.' ||
1762 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1765 version_super = ptr_cookie2[0] - '0';
1766 version_major = ptr_cookie2[2] - '0';
1768 return VERSION_IDENT(version_super, version_major, 0, 0);
1771 boolean checkCookieString(const char *cookie, const char *template)
1773 const char *pattern = "_FILE_VERSION_?.?";
1774 const int len_cookie = strlen(cookie);
1775 const int len_template = strlen(template);
1776 const int len_pattern = strlen(pattern);
1778 if (len_cookie != len_template)
1781 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1788 // ----------------------------------------------------------------------------
1789 // setup file list and hash handling functions
1790 // ----------------------------------------------------------------------------
1792 char *getFormattedSetupEntry(char *token, char *value)
1795 static char entry[MAX_LINE_LEN];
1797 // if value is an empty string, just return token without value
1801 // start with the token and some spaces to format output line
1802 sprintf(entry, "%s:", token);
1803 for (i = strlen(entry); i < token_value_position; i++)
1806 // continue with the token's value
1807 strcat(entry, value);
1812 SetupFileList *newSetupFileList(char *token, char *value)
1814 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1816 new->token = getStringCopy(token);
1817 new->value = getStringCopy(value);
1824 void freeSetupFileList(SetupFileList *list)
1829 checked_free(list->token);
1830 checked_free(list->value);
1833 freeSetupFileList(list->next);
1838 char *getListEntry(SetupFileList *list, char *token)
1843 if (strEqual(list->token, token))
1846 return getListEntry(list->next, token);
1849 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
1854 if (strEqual(list->token, token))
1856 checked_free(list->value);
1858 list->value = getStringCopy(value);
1862 else if (list->next == NULL)
1863 return (list->next = newSetupFileList(token, value));
1865 return setListEntry(list->next, token, value);
1868 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
1873 if (list->next == NULL)
1874 return (list->next = newSetupFileList(token, value));
1876 return addListEntry(list->next, token, value);
1879 #if ENABLE_UNUSED_CODE
1881 static void printSetupFileList(SetupFileList *list)
1886 Debug("setup:printSetupFileList", "token: '%s'", list->token);
1887 Debug("setup:printSetupFileList", "value: '%s'", list->value);
1889 printSetupFileList(list->next);
1895 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1896 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1897 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1898 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1900 #define insert_hash_entry hashtable_insert
1901 #define search_hash_entry hashtable_search
1902 #define change_hash_entry hashtable_change
1903 #define remove_hash_entry hashtable_remove
1906 unsigned int get_hash_from_key(void *key)
1911 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1912 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1913 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1914 it works better than many other constants, prime or not) has never been
1915 adequately explained.
1917 If you just want to have a good hash function, and cannot wait, djb2
1918 is one of the best string hash functions i know. It has excellent
1919 distribution and speed on many different sets of keys and table sizes.
1920 You are not likely to do better with one of the "well known" functions
1921 such as PJW, K&R, etc.
1923 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1926 char *str = (char *)key;
1927 unsigned int hash = 5381;
1930 while ((c = *str++))
1931 hash = ((hash << 5) + hash) + c; // hash * 33 + c
1936 static int keys_are_equal(void *key1, void *key2)
1938 return (strEqual((char *)key1, (char *)key2));
1941 SetupFileHash *newSetupFileHash(void)
1943 SetupFileHash *new_hash =
1944 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1946 if (new_hash == NULL)
1947 Fail("create_hashtable() failed -- out of memory");
1952 void freeSetupFileHash(SetupFileHash *hash)
1957 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
1960 char *getHashEntry(SetupFileHash *hash, char *token)
1965 return search_hash_entry(hash, token);
1968 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1975 value_copy = getStringCopy(value);
1977 // change value; if it does not exist, insert it as new
1978 if (!change_hash_entry(hash, token, value_copy))
1979 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1980 Fail("cannot insert into hash -- aborting");
1983 char *removeHashEntry(SetupFileHash *hash, char *token)
1988 return remove_hash_entry(hash, token);
1991 #if ENABLE_UNUSED_CODE
1993 static void printSetupFileHash(SetupFileHash *hash)
1995 BEGIN_HASH_ITERATION(hash, itr)
1997 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
1998 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2000 END_HASH_ITERATION(hash, itr)
2005 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2006 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2007 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2009 static boolean token_value_separator_found = FALSE;
2010 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2011 static boolean token_value_separator_warning = FALSE;
2013 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2014 static boolean token_already_exists_warning = FALSE;
2017 static boolean getTokenValueFromSetupLineExt(char *line,
2018 char **token_ptr, char **value_ptr,
2019 char *filename, char *line_raw,
2021 boolean separator_required)
2023 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2024 char *token, *value, *line_ptr;
2026 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2027 if (line_raw == NULL)
2029 strncpy(line_copy, line, MAX_LINE_LEN);
2030 line_copy[MAX_LINE_LEN] = '\0';
2033 strcpy(line_raw_copy, line_copy);
2034 line_raw = line_raw_copy;
2037 // cut trailing comment from input line
2038 for (line_ptr = line; *line_ptr; line_ptr++)
2040 if (*line_ptr == '#')
2047 // cut trailing whitespaces from input line
2048 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2049 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2052 // ignore empty lines
2056 // cut leading whitespaces from token
2057 for (token = line; *token; token++)
2058 if (*token != ' ' && *token != '\t')
2061 // start with empty value as reliable default
2064 token_value_separator_found = FALSE;
2066 // find end of token to determine start of value
2067 for (line_ptr = token; *line_ptr; line_ptr++)
2069 // first look for an explicit token/value separator, like ':' or '='
2070 if (*line_ptr == ':' || *line_ptr == '=')
2072 *line_ptr = '\0'; // terminate token string
2073 value = line_ptr + 1; // set beginning of value
2075 token_value_separator_found = TRUE;
2081 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2082 // fallback: if no token/value separator found, also allow whitespaces
2083 if (!token_value_separator_found && !separator_required)
2085 for (line_ptr = token; *line_ptr; line_ptr++)
2087 if (*line_ptr == ' ' || *line_ptr == '\t')
2089 *line_ptr = '\0'; // terminate token string
2090 value = line_ptr + 1; // set beginning of value
2092 token_value_separator_found = TRUE;
2098 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2099 if (token_value_separator_found)
2101 if (!token_value_separator_warning)
2103 Debug("setup", "---");
2105 if (filename != NULL)
2107 Debug("setup", "missing token/value separator(s) in config file:");
2108 Debug("setup", "- config file: '%s'", filename);
2112 Debug("setup", "missing token/value separator(s):");
2115 token_value_separator_warning = TRUE;
2118 if (filename != NULL)
2119 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2121 Debug("setup", "- line: '%s'", line_raw);
2127 // cut trailing whitespaces from token
2128 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2129 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2132 // cut leading whitespaces from value
2133 for (; *value; value++)
2134 if (*value != ' ' && *value != '\t')
2143 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2145 // while the internal (old) interface does not require a token/value
2146 // separator (for downwards compatibility with existing files which
2147 // don't use them), it is mandatory for the external (new) interface
2149 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2152 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2153 boolean top_recursion_level, boolean is_hash)
2155 static SetupFileHash *include_filename_hash = NULL;
2156 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2157 char *token, *value, *line_ptr;
2158 void *insert_ptr = NULL;
2159 boolean read_continued_line = FALSE;
2161 int line_nr = 0, token_count = 0, include_count = 0;
2163 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2164 token_value_separator_warning = FALSE;
2167 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2168 token_already_exists_warning = FALSE;
2171 if (!(file = openFile(filename, MODE_READ)))
2173 #if DEBUG_NO_CONFIG_FILE
2174 Debug("setup", "cannot open configuration file '%s'", filename);
2180 // use "insert pointer" to store list end for constant insertion complexity
2182 insert_ptr = setup_file_data;
2184 // on top invocation, create hash to mark included files (to prevent loops)
2185 if (top_recursion_level)
2186 include_filename_hash = newSetupFileHash();
2188 // mark this file as already included (to prevent including it again)
2189 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2191 while (!checkEndOfFile(file))
2193 // read next line of input file
2194 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2197 // check if line was completely read and is terminated by line break
2198 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2201 // cut trailing line break (this can be newline and/or carriage return)
2202 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2203 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2206 // copy raw input line for later use (mainly debugging output)
2207 strcpy(line_raw, line);
2209 if (read_continued_line)
2211 // append new line to existing line, if there is enough space
2212 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2213 strcat(previous_line, line_ptr);
2215 strcpy(line, previous_line); // copy storage buffer to line
2217 read_continued_line = FALSE;
2220 // if the last character is '\', continue at next line
2221 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2223 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2224 strcpy(previous_line, line); // copy line to storage buffer
2226 read_continued_line = TRUE;
2231 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2232 line_raw, line_nr, FALSE))
2237 if (strEqual(token, "include"))
2239 if (getHashEntry(include_filename_hash, value) == NULL)
2241 char *basepath = getBasePath(filename);
2242 char *basename = getBaseName(value);
2243 char *filename_include = getPath2(basepath, basename);
2245 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2249 free(filename_include);
2255 Warn("ignoring already processed file '%s'", value);
2262 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2264 getHashEntry((SetupFileHash *)setup_file_data, token);
2266 if (old_value != NULL)
2268 if (!token_already_exists_warning)
2270 Debug("setup", "---");
2271 Debug("setup", "duplicate token(s) found in config file:");
2272 Debug("setup", "- config file: '%s'", filename);
2274 token_already_exists_warning = TRUE;
2277 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2278 Debug("setup", " old value: '%s'", old_value);
2279 Debug("setup", " new value: '%s'", value);
2283 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2287 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2297 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2298 if (token_value_separator_warning)
2299 Debug("setup", "---");
2302 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2303 if (token_already_exists_warning)
2304 Debug("setup", "---");
2307 if (token_count == 0 && include_count == 0)
2308 Warn("configuration file '%s' is empty", filename);
2310 if (top_recursion_level)
2311 freeSetupFileHash(include_filename_hash);
2316 static int compareSetupFileData(const void *object1, const void *object2)
2318 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2319 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2321 return strcmp(entry1->token, entry2->token);
2324 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2326 int item_count = hashtable_count(hash);
2327 int item_size = sizeof(struct ConfigInfo);
2328 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2332 // copy string pointers from hash to array
2333 BEGIN_HASH_ITERATION(hash, itr)
2335 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2336 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2340 if (i > item_count) // should never happen
2343 END_HASH_ITERATION(hash, itr)
2345 // sort string pointers from hash in array
2346 qsort(sort_array, item_count, item_size, compareSetupFileData);
2348 if (!(file = fopen(filename, MODE_WRITE)))
2350 Warn("cannot write configuration file '%s'", filename);
2355 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2356 program.version_string));
2357 for (i = 0; i < item_count; i++)
2358 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2359 sort_array[i].value));
2362 checked_free(sort_array);
2365 SetupFileList *loadSetupFileList(char *filename)
2367 SetupFileList *setup_file_list = newSetupFileList("", "");
2368 SetupFileList *first_valid_list_entry;
2370 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2372 freeSetupFileList(setup_file_list);
2377 first_valid_list_entry = setup_file_list->next;
2379 // free empty list header
2380 setup_file_list->next = NULL;
2381 freeSetupFileList(setup_file_list);
2383 return first_valid_list_entry;
2386 SetupFileHash *loadSetupFileHash(char *filename)
2388 SetupFileHash *setup_file_hash = newSetupFileHash();
2390 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2392 freeSetupFileHash(setup_file_hash);
2397 return setup_file_hash;
2401 // ============================================================================
2403 // ============================================================================
2405 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2406 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2407 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2408 #define TOKEN_STR_LAST_USER "last_user"
2410 // level directory info
2411 #define LEVELINFO_TOKEN_IDENTIFIER 0
2412 #define LEVELINFO_TOKEN_NAME 1
2413 #define LEVELINFO_TOKEN_NAME_SORTING 2
2414 #define LEVELINFO_TOKEN_AUTHOR 3
2415 #define LEVELINFO_TOKEN_YEAR 4
2416 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2417 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2418 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2419 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2420 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2421 #define LEVELINFO_TOKEN_TESTED_BY 10
2422 #define LEVELINFO_TOKEN_LEVELS 11
2423 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2424 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2425 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2426 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2427 #define LEVELINFO_TOKEN_READONLY 16
2428 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2429 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2430 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2431 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2432 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2433 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2434 #define LEVELINFO_TOKEN_MUSIC_SET 23
2435 #define LEVELINFO_TOKEN_FILENAME 24
2436 #define LEVELINFO_TOKEN_FILETYPE 25
2437 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2438 #define LEVELINFO_TOKEN_HANDICAP 27
2439 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2440 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2442 #define NUM_LEVELINFO_TOKENS 30
2444 static LevelDirTree ldi;
2446 static struct TokenInfo levelinfo_tokens[] =
2448 // level directory info
2449 { TYPE_STRING, &ldi.identifier, "identifier" },
2450 { TYPE_STRING, &ldi.name, "name" },
2451 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2452 { TYPE_STRING, &ldi.author, "author" },
2453 { TYPE_STRING, &ldi.year, "year" },
2454 { TYPE_STRING, &ldi.program_title, "program_title" },
2455 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2456 { TYPE_STRING, &ldi.program_company, "program_company" },
2457 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2458 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2459 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2460 { TYPE_INTEGER, &ldi.levels, "levels" },
2461 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2462 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2463 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2464 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2465 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2466 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2467 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2468 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2469 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2470 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2471 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2472 { TYPE_STRING, &ldi.music_set, "music_set" },
2473 { TYPE_STRING, &ldi.level_filename, "filename" },
2474 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2475 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2476 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2477 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2478 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2481 static struct TokenInfo artworkinfo_tokens[] =
2483 // artwork directory info
2484 { TYPE_STRING, &ldi.identifier, "identifier" },
2485 { TYPE_STRING, &ldi.subdir, "subdir" },
2486 { TYPE_STRING, &ldi.name, "name" },
2487 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2488 { TYPE_STRING, &ldi.author, "author" },
2489 { TYPE_STRING, &ldi.program_title, "program_title" },
2490 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2491 { TYPE_STRING, &ldi.program_company, "program_company" },
2492 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2493 { TYPE_STRING, &ldi.basepath, "basepath" },
2494 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2495 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2496 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2501 static char *optional_tokens[] =
2504 "program_copyright",
2510 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2514 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2515 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2516 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2517 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2520 ti->node_parent = NULL;
2521 ti->node_group = NULL;
2528 ti->fullpath = NULL;
2529 ti->basepath = NULL;
2530 ti->identifier = NULL;
2531 ti->name = getStringCopy(ANONYMOUS_NAME);
2532 ti->name_sorting = NULL;
2533 ti->author = getStringCopy(ANONYMOUS_NAME);
2536 ti->program_title = NULL;
2537 ti->program_copyright = NULL;
2538 ti->program_company = NULL;
2540 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2541 ti->latest_engine = FALSE; // default: get from level
2542 ti->parent_link = FALSE;
2543 ti->is_copy = FALSE;
2544 ti->in_user_dir = FALSE;
2545 ti->user_defined = FALSE;
2547 ti->class_desc = NULL;
2549 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2551 if (ti->type == TREE_TYPE_LEVEL_DIR)
2553 ti->imported_from = NULL;
2554 ti->imported_by = NULL;
2555 ti->tested_by = NULL;
2557 ti->graphics_set_ecs = NULL;
2558 ti->graphics_set_aga = NULL;
2559 ti->graphics_set = NULL;
2560 ti->sounds_set_default = NULL;
2561 ti->sounds_set_lowpass = NULL;
2562 ti->sounds_set = NULL;
2563 ti->music_set = NULL;
2564 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2565 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2566 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2568 ti->level_filename = NULL;
2569 ti->level_filetype = NULL;
2571 ti->special_flags = NULL;
2574 ti->first_level = 0;
2576 ti->level_group = FALSE;
2577 ti->handicap_level = 0;
2578 ti->readonly = TRUE;
2579 ti->handicap = TRUE;
2580 ti->skip_levels = FALSE;
2582 ti->use_emc_tiles = FALSE;
2586 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2590 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2592 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2597 // copy all values from the parent structure
2599 ti->type = parent->type;
2601 ti->node_top = parent->node_top;
2602 ti->node_parent = parent;
2603 ti->node_group = NULL;
2610 ti->fullpath = NULL;
2611 ti->basepath = NULL;
2612 ti->identifier = NULL;
2613 ti->name = getStringCopy(ANONYMOUS_NAME);
2614 ti->name_sorting = NULL;
2615 ti->author = getStringCopy(parent->author);
2616 ti->year = getStringCopy(parent->year);
2618 ti->program_title = getStringCopy(parent->program_title);
2619 ti->program_copyright = getStringCopy(parent->program_copyright);
2620 ti->program_company = getStringCopy(parent->program_company);
2622 ti->sort_priority = parent->sort_priority;
2623 ti->latest_engine = parent->latest_engine;
2624 ti->parent_link = FALSE;
2625 ti->is_copy = FALSE;
2626 ti->in_user_dir = parent->in_user_dir;
2627 ti->user_defined = parent->user_defined;
2628 ti->color = parent->color;
2629 ti->class_desc = getStringCopy(parent->class_desc);
2631 ti->infotext = getStringCopy(parent->infotext);
2633 if (ti->type == TREE_TYPE_LEVEL_DIR)
2635 ti->imported_from = getStringCopy(parent->imported_from);
2636 ti->imported_by = getStringCopy(parent->imported_by);
2637 ti->tested_by = getStringCopy(parent->tested_by);
2639 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2640 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2641 ti->graphics_set = getStringCopy(parent->graphics_set);
2642 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2643 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2644 ti->sounds_set = getStringCopy(parent->sounds_set);
2645 ti->music_set = getStringCopy(parent->music_set);
2646 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2647 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2648 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2650 ti->level_filename = getStringCopy(parent->level_filename);
2651 ti->level_filetype = getStringCopy(parent->level_filetype);
2653 ti->special_flags = getStringCopy(parent->special_flags);
2655 ti->levels = parent->levels;
2656 ti->first_level = parent->first_level;
2657 ti->last_level = parent->last_level;
2658 ti->level_group = FALSE;
2659 ti->handicap_level = parent->handicap_level;
2660 ti->readonly = parent->readonly;
2661 ti->handicap = parent->handicap;
2662 ti->skip_levels = parent->skip_levels;
2664 ti->use_emc_tiles = parent->use_emc_tiles;
2668 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2670 TreeInfo *ti_copy = newTreeInfo();
2672 // copy all values from the original structure
2674 ti_copy->type = ti->type;
2676 ti_copy->node_top = ti->node_top;
2677 ti_copy->node_parent = ti->node_parent;
2678 ti_copy->node_group = ti->node_group;
2679 ti_copy->next = ti->next;
2681 ti_copy->cl_first = ti->cl_first;
2682 ti_copy->cl_cursor = ti->cl_cursor;
2684 ti_copy->subdir = getStringCopy(ti->subdir);
2685 ti_copy->fullpath = getStringCopy(ti->fullpath);
2686 ti_copy->basepath = getStringCopy(ti->basepath);
2687 ti_copy->identifier = getStringCopy(ti->identifier);
2688 ti_copy->name = getStringCopy(ti->name);
2689 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2690 ti_copy->author = getStringCopy(ti->author);
2691 ti_copy->year = getStringCopy(ti->year);
2693 ti_copy->program_title = getStringCopy(ti->program_title);
2694 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2695 ti_copy->program_company = getStringCopy(ti->program_company);
2697 ti_copy->imported_from = getStringCopy(ti->imported_from);
2698 ti_copy->imported_by = getStringCopy(ti->imported_by);
2699 ti_copy->tested_by = getStringCopy(ti->tested_by);
2701 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2702 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2703 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2704 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2705 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2706 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2707 ti_copy->music_set = getStringCopy(ti->music_set);
2708 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2709 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2710 ti_copy->music_path = getStringCopy(ti->music_path);
2712 ti_copy->level_filename = getStringCopy(ti->level_filename);
2713 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2715 ti_copy->special_flags = getStringCopy(ti->special_flags);
2717 ti_copy->levels = ti->levels;
2718 ti_copy->first_level = ti->first_level;
2719 ti_copy->last_level = ti->last_level;
2720 ti_copy->sort_priority = ti->sort_priority;
2722 ti_copy->latest_engine = ti->latest_engine;
2724 ti_copy->level_group = ti->level_group;
2725 ti_copy->parent_link = ti->parent_link;
2726 ti_copy->is_copy = ti->is_copy;
2727 ti_copy->in_user_dir = ti->in_user_dir;
2728 ti_copy->user_defined = ti->user_defined;
2729 ti_copy->readonly = ti->readonly;
2730 ti_copy->handicap = ti->handicap;
2731 ti_copy->skip_levels = ti->skip_levels;
2733 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2735 ti_copy->color = ti->color;
2736 ti_copy->class_desc = getStringCopy(ti->class_desc);
2737 ti_copy->handicap_level = ti->handicap_level;
2739 ti_copy->infotext = getStringCopy(ti->infotext);
2744 void freeTreeInfo(TreeInfo *ti)
2749 checked_free(ti->subdir);
2750 checked_free(ti->fullpath);
2751 checked_free(ti->basepath);
2752 checked_free(ti->identifier);
2754 checked_free(ti->name);
2755 checked_free(ti->name_sorting);
2756 checked_free(ti->author);
2757 checked_free(ti->year);
2759 checked_free(ti->program_title);
2760 checked_free(ti->program_copyright);
2761 checked_free(ti->program_company);
2763 checked_free(ti->class_desc);
2765 checked_free(ti->infotext);
2767 if (ti->type == TREE_TYPE_LEVEL_DIR)
2769 checked_free(ti->imported_from);
2770 checked_free(ti->imported_by);
2771 checked_free(ti->tested_by);
2773 checked_free(ti->graphics_set_ecs);
2774 checked_free(ti->graphics_set_aga);
2775 checked_free(ti->graphics_set);
2776 checked_free(ti->sounds_set_default);
2777 checked_free(ti->sounds_set_lowpass);
2778 checked_free(ti->sounds_set);
2779 checked_free(ti->music_set);
2781 checked_free(ti->graphics_path);
2782 checked_free(ti->sounds_path);
2783 checked_free(ti->music_path);
2785 checked_free(ti->level_filename);
2786 checked_free(ti->level_filetype);
2788 checked_free(ti->special_flags);
2791 // recursively free child node
2793 freeTreeInfo(ti->node_group);
2795 // recursively free next node
2797 freeTreeInfo(ti->next);
2802 void setSetupInfo(struct TokenInfo *token_info,
2803 int token_nr, char *token_value)
2805 int token_type = token_info[token_nr].type;
2806 void *setup_value = token_info[token_nr].value;
2808 if (token_value == NULL)
2811 // set setup field to corresponding token value
2816 *(boolean *)setup_value = get_boolean_from_string(token_value);
2820 *(int *)setup_value = get_switch3_from_string(token_value);
2824 *(Key *)setup_value = getKeyFromKeyName(token_value);
2828 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
2832 *(int *)setup_value = get_integer_from_string(token_value);
2836 checked_free(*(char **)setup_value);
2837 *(char **)setup_value = getStringCopy(token_value);
2841 *(int *)setup_value = get_player_nr_from_string(token_value);
2849 static int compareTreeInfoEntries(const void *object1, const void *object2)
2851 const TreeInfo *entry1 = *((TreeInfo **)object1);
2852 const TreeInfo *entry2 = *((TreeInfo **)object2);
2853 int tree_sorting1 = TREE_SORTING(entry1);
2854 int tree_sorting2 = TREE_SORTING(entry2);
2856 if (tree_sorting1 != tree_sorting2)
2857 return (tree_sorting1 - tree_sorting2);
2859 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
2862 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
2866 if (node_parent == NULL)
2869 ti_new = newTreeInfo();
2870 setTreeInfoToDefaults(ti_new, node_parent->type);
2872 ti_new->node_parent = node_parent;
2873 ti_new->parent_link = TRUE;
2875 setString(&ti_new->identifier, node_parent->identifier);
2876 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
2877 setString(&ti_new->name_sorting, ti_new->name);
2879 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
2880 setString(&ti_new->fullpath, node_parent->fullpath);
2882 ti_new->sort_priority = LEVELCLASS_PARENT;
2883 ti_new->latest_engine = node_parent->latest_engine;
2885 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
2887 pushTreeInfo(&node_parent->node_group, ti_new);
2892 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
2894 if (node_first == NULL)
2897 TreeInfo *ti_new = newTreeInfo();
2898 int type = node_first->type;
2900 setTreeInfoToDefaults(ti_new, type);
2902 ti_new->node_parent = NULL;
2903 ti_new->parent_link = FALSE;
2905 setString(&ti_new->identifier, "top_tree_node");
2906 setString(&ti_new->name, TREE_INFOTEXT(type));
2907 setString(&ti_new->name_sorting, ti_new->name);
2909 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
2910 setString(&ti_new->fullpath, ".");
2912 ti_new->sort_priority = LEVELCLASS_TOP;
2913 ti_new->latest_engine = node_first->latest_engine;
2915 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
2917 ti_new->node_group = node_first;
2918 ti_new->level_group = TRUE;
2920 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
2922 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
2923 setString(&ti_new2->name_sorting, ti_new2->name);
2928 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
2932 if (node->node_group)
2933 setTreeInfoParentNodes(node->node_group, node);
2935 node->node_parent = node_parent;
2942 // ----------------------------------------------------------------------------
2943 // functions for handling level and custom artwork info cache
2944 // ----------------------------------------------------------------------------
2946 static void LoadArtworkInfoCache(void)
2948 InitCacheDirectory();
2950 if (artworkinfo_cache_old == NULL)
2952 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2954 // try to load artwork info hash from already existing cache file
2955 artworkinfo_cache_old = loadSetupFileHash(filename);
2957 // try to get program version that artwork info cache was written with
2958 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
2960 // check program version of artwork info cache against current version
2961 if (!strEqual(version, program.version_string))
2963 freeSetupFileHash(artworkinfo_cache_old);
2965 artworkinfo_cache_old = NULL;
2968 // if no artwork info cache file was found, start with empty hash
2969 if (artworkinfo_cache_old == NULL)
2970 artworkinfo_cache_old = newSetupFileHash();
2975 if (artworkinfo_cache_new == NULL)
2976 artworkinfo_cache_new = newSetupFileHash();
2978 update_artworkinfo_cache = FALSE;
2981 static void SaveArtworkInfoCache(void)
2983 if (!update_artworkinfo_cache)
2986 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
2988 InitCacheDirectory();
2990 saveSetupFileHash(artworkinfo_cache_new, filename);
2995 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
2997 static char *prefix = NULL;
2999 checked_free(prefix);
3001 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3006 // (identical to above function, but separate string buffer needed -- nasty)
3007 static char *getCacheToken(char *prefix, char *suffix)
3009 static char *token = NULL;
3011 checked_free(token);
3013 token = getStringCat2WithSeparator(prefix, suffix, ".");
3018 static char *getFileTimestampString(char *filename)
3020 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3023 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3025 struct stat file_status;
3027 if (timestamp_string == NULL)
3030 if (!fileExists(filename)) // file does not exist
3031 return (atoi(timestamp_string) != 0);
3033 if (stat(filename, &file_status) != 0) // cannot stat file
3036 return (file_status.st_mtime != atoi(timestamp_string));
3039 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3041 char *identifier = level_node->subdir;
3042 char *type_string = ARTWORK_DIRECTORY(type);
3043 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3044 char *token_main = getCacheToken(token_prefix, "CACHED");
3045 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3046 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3047 TreeInfo *artwork_info = NULL;
3049 if (!use_artworkinfo_cache)
3052 if (optional_tokens_hash == NULL)
3056 // create hash from list of optional tokens (for quick access)
3057 optional_tokens_hash = newSetupFileHash();
3058 for (i = 0; optional_tokens[i] != NULL; i++)
3059 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3066 artwork_info = newTreeInfo();
3067 setTreeInfoToDefaults(artwork_info, type);
3069 // set all structure fields according to the token/value pairs
3070 ldi = *artwork_info;
3071 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3073 char *token_suffix = artworkinfo_tokens[i].text;
3074 char *token = getCacheToken(token_prefix, token_suffix);
3075 char *value = getHashEntry(artworkinfo_cache_old, token);
3077 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3079 setSetupInfo(artworkinfo_tokens, i, value);
3081 // check if cache entry for this item is mandatory, but missing
3082 if (value == NULL && !optional)
3084 Warn("missing cache entry '%s'", token);
3090 *artwork_info = ldi;
3095 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3096 LEVELINFO_FILENAME);
3097 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3098 ARTWORKINFO_FILENAME(type));
3100 // check if corresponding "levelinfo.conf" file has changed
3101 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3102 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3104 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3107 // check if corresponding "<artworkinfo>.conf" file has changed
3108 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3109 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3111 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3114 checked_free(filename_levelinfo);
3115 checked_free(filename_artworkinfo);
3118 if (!cached && artwork_info != NULL)
3120 freeTreeInfo(artwork_info);
3125 return artwork_info;
3128 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3129 LevelDirTree *level_node, int type)
3131 char *identifier = level_node->subdir;
3132 char *type_string = ARTWORK_DIRECTORY(type);
3133 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3134 char *token_main = getCacheToken(token_prefix, "CACHED");
3135 boolean set_cache_timestamps = TRUE;
3138 setHashEntry(artworkinfo_cache_new, token_main, "true");
3140 if (set_cache_timestamps)
3142 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3143 LEVELINFO_FILENAME);
3144 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3145 ARTWORKINFO_FILENAME(type));
3146 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3147 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3149 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3150 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3152 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3153 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3155 checked_free(filename_levelinfo);
3156 checked_free(filename_artworkinfo);
3157 checked_free(timestamp_levelinfo);
3158 checked_free(timestamp_artworkinfo);
3161 ldi = *artwork_info;
3162 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3164 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3165 char *value = getSetupValue(artworkinfo_tokens[i].type,
3166 artworkinfo_tokens[i].value);
3168 setHashEntry(artworkinfo_cache_new, token, value);
3173 // ----------------------------------------------------------------------------
3174 // functions for loading level info and custom artwork info
3175 // ----------------------------------------------------------------------------
3177 int GetZipFileTreeType(char *zip_filename)
3179 static char *top_dir_path = NULL;
3180 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3181 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3183 GRAPHICSINFO_FILENAME,
3184 SOUNDSINFO_FILENAME,
3190 checked_free(top_dir_path);
3191 top_dir_path = NULL;
3193 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3195 checked_free(top_dir_conf_filename[j]);
3196 top_dir_conf_filename[j] = NULL;
3199 char **zip_entries = zip_list(zip_filename);
3201 // check if zip file successfully opened
3202 if (zip_entries == NULL || zip_entries[0] == NULL)
3203 return TREE_TYPE_UNDEFINED;
3205 // first zip file entry is expected to be top level directory
3206 char *top_dir = zip_entries[0];
3208 // check if valid top level directory found in zip file
3209 if (!strSuffix(top_dir, "/"))
3210 return TREE_TYPE_UNDEFINED;
3212 // get filenames of valid configuration files in top level directory
3213 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3214 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3216 int tree_type = TREE_TYPE_UNDEFINED;
3219 while (zip_entries[e] != NULL)
3221 // check if every zip file entry is below top level directory
3222 if (!strPrefix(zip_entries[e], top_dir))
3223 return TREE_TYPE_UNDEFINED;
3225 // check if this zip file entry is a valid configuration filename
3226 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3228 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3230 // only exactly one valid configuration file allowed
3231 if (tree_type != TREE_TYPE_UNDEFINED)
3232 return TREE_TYPE_UNDEFINED;
3244 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3247 static char *top_dir_path = NULL;
3248 static char *top_dir_conf_filename = NULL;
3250 checked_free(top_dir_path);
3251 checked_free(top_dir_conf_filename);
3253 top_dir_path = NULL;
3254 top_dir_conf_filename = NULL;
3256 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3257 ARTWORKINFO_FILENAME(tree_type));
3259 // check if valid configuration filename determined
3260 if (conf_basename == NULL || strEqual(conf_basename, ""))
3263 char **zip_entries = zip_list(zip_filename);
3265 // check if zip file successfully opened
3266 if (zip_entries == NULL || zip_entries[0] == NULL)
3269 // first zip file entry is expected to be top level directory
3270 char *top_dir = zip_entries[0];
3272 // check if valid top level directory found in zip file
3273 if (!strSuffix(top_dir, "/"))
3276 // get path of extracted top level directory
3277 top_dir_path = getPath2(directory, top_dir);
3279 // remove trailing directory separator from top level directory path
3280 // (required to be able to check for file and directory in next step)
3281 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3283 // check if zip file's top level directory already exists in target directory
3284 if (fileExists(top_dir_path)) // (checks for file and directory)
3287 // get filename of configuration file in top level directory
3288 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3290 boolean found_top_dir_conf_filename = FALSE;
3293 while (zip_entries[i] != NULL)
3295 // check if every zip file entry is below top level directory
3296 if (!strPrefix(zip_entries[i], top_dir))
3299 // check if this zip file entry is the configuration filename
3300 if (strEqual(zip_entries[i], top_dir_conf_filename))
3301 found_top_dir_conf_filename = TRUE;
3306 // check if valid configuration filename was found in zip file
3307 if (!found_top_dir_conf_filename)
3313 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3316 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3319 if (!zip_file_valid)
3321 Warn("zip file '%s' rejected!", zip_filename);
3326 char **zip_entries = zip_extract(zip_filename, directory);
3328 if (zip_entries == NULL)
3330 Warn("zip file '%s' could not be extracted!", zip_filename);
3335 Info("zip file '%s' successfully extracted!", zip_filename);
3337 // first zip file entry contains top level directory
3338 char *top_dir = zip_entries[0];
3340 // remove trailing directory separator from top level directory
3341 top_dir[strlen(top_dir) - 1] = '\0';
3346 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3349 DirectoryEntry *dir_entry;
3351 if ((dir = openDirectory(directory)) == NULL)
3353 // display error if directory is main "options.graphics_directory" etc.
3354 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3355 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3356 Warn("cannot read directory '%s'", directory);
3361 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3363 // skip non-zip files (and also directories with zip extension)
3364 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3367 char *zip_filename = getPath2(directory, dir_entry->basename);
3368 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3369 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3371 // check if zip file hasn't already been extracted or rejected
3372 if (!fileExists(zip_filename_extracted) &&
3373 !fileExists(zip_filename_rejected))
3375 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3377 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3378 zip_filename_rejected);
3381 // create empty file to mark zip file as extracted or rejected
3382 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3383 fclose(marker_file);
3386 free(zip_filename_extracted);
3387 free(zip_filename_rejected);
3391 closeDirectory(dir);
3394 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3395 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3397 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3398 TreeInfo *node_parent,
3399 char *level_directory,
3400 char *directory_name)
3402 char *directory_path = getPath2(level_directory, directory_name);
3403 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3404 SetupFileHash *setup_file_hash;
3405 LevelDirTree *leveldir_new = NULL;
3408 // unless debugging, silently ignore directories without "levelinfo.conf"
3409 if (!options.debug && !fileExists(filename))
3411 free(directory_path);
3417 setup_file_hash = loadSetupFileHash(filename);
3419 if (setup_file_hash == NULL)
3421 #if DEBUG_NO_CONFIG_FILE
3422 Debug("setup", "ignoring level directory '%s'", directory_path);
3425 free(directory_path);
3431 leveldir_new = newTreeInfo();
3434 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3436 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3438 leveldir_new->subdir = getStringCopy(directory_name);
3440 // set all structure fields according to the token/value pairs
3441 ldi = *leveldir_new;
3442 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3443 setSetupInfo(levelinfo_tokens, i,
3444 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3445 *leveldir_new = ldi;
3447 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3448 setString(&leveldir_new->name, leveldir_new->subdir);
3450 if (leveldir_new->identifier == NULL)
3451 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3453 if (leveldir_new->name_sorting == NULL)
3454 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3456 if (node_parent == NULL) // top level group
3458 leveldir_new->basepath = getStringCopy(level_directory);
3459 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3461 else // sub level group
3463 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3464 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3467 leveldir_new->last_level =
3468 leveldir_new->first_level + leveldir_new->levels - 1;
3470 leveldir_new->in_user_dir =
3471 (!strEqual(leveldir_new->basepath, options.level_directory));
3473 // adjust some settings if user's private level directory was detected
3474 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3475 leveldir_new->in_user_dir &&
3476 (strEqual(leveldir_new->subdir, getLoginName()) ||
3477 strEqual(leveldir_new->name, getLoginName()) ||
3478 strEqual(leveldir_new->author, getRealName())))
3480 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3481 leveldir_new->readonly = FALSE;
3484 leveldir_new->user_defined =
3485 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3487 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3489 leveldir_new->handicap_level = // set handicap to default value
3490 (leveldir_new->user_defined || !leveldir_new->handicap ?
3491 leveldir_new->last_level : leveldir_new->first_level);
3493 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
3495 pushTreeInfo(node_first, leveldir_new);
3497 freeSetupFileHash(setup_file_hash);
3499 if (leveldir_new->level_group)
3501 // create node to link back to current level directory
3502 createParentTreeInfoNode(leveldir_new);
3504 // recursively step into sub-directory and look for more level series
3505 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3506 leveldir_new, directory_path);
3509 free(directory_path);
3515 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3516 TreeInfo *node_parent,
3517 char *level_directory)
3519 // ---------- 1st stage: process any level set zip files ----------
3521 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3523 // ---------- 2nd stage: check for level set directories ----------
3526 DirectoryEntry *dir_entry;
3527 boolean valid_entry_found = FALSE;
3529 if ((dir = openDirectory(level_directory)) == NULL)
3531 Warn("cannot read level directory '%s'", level_directory);
3536 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3538 char *directory_name = dir_entry->basename;
3539 char *directory_path = getPath2(level_directory, directory_name);
3541 // skip entries for current and parent directory
3542 if (strEqual(directory_name, ".") ||
3543 strEqual(directory_name, ".."))
3545 free(directory_path);
3550 // find out if directory entry is itself a directory
3551 if (!dir_entry->is_directory) // not a directory
3553 free(directory_path);
3558 free(directory_path);
3560 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3561 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3562 strEqual(directory_name, MUSIC_DIRECTORY))
3565 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3570 closeDirectory(dir);
3572 // special case: top level directory may directly contain "levelinfo.conf"
3573 if (node_parent == NULL && !valid_entry_found)
3575 // check if this directory directly contains a file "levelinfo.conf"
3576 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3577 level_directory, ".");
3580 if (!valid_entry_found)
3581 Warn("cannot find any valid level series in directory '%s'",
3585 boolean AdjustGraphicsForEMC(void)
3587 boolean settings_changed = FALSE;
3589 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3590 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3592 return settings_changed;
3595 boolean AdjustSoundsForEMC(void)
3597 boolean settings_changed = FALSE;
3599 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3600 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3602 return settings_changed;
3605 void LoadLevelInfo(void)
3607 InitUserLevelDirectory(getLoginName());
3609 DrawInitText("Loading level series", 120, FC_GREEN);
3611 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3612 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3614 leveldir_first = createTopTreeInfoNode(leveldir_first);
3616 /* after loading all level set information, clone the level directory tree
3617 and remove all level sets without levels (these may still contain artwork
3618 to be offered in the setup menu as "custom artwork", and are therefore
3619 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3620 leveldir_first_all = leveldir_first;
3621 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3623 AdjustGraphicsForEMC();
3624 AdjustSoundsForEMC();
3626 // before sorting, the first entries will be from the user directory
3627 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3629 if (leveldir_first == NULL)
3630 Fail("cannot find any valid level series in any directory");
3632 sortTreeInfo(&leveldir_first);
3634 #if ENABLE_UNUSED_CODE
3635 dumpTreeInfo(leveldir_first, 0);
3639 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3640 TreeInfo *node_parent,
3641 char *base_directory,
3642 char *directory_name, int type)
3644 char *directory_path = getPath2(base_directory, directory_name);
3645 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3646 SetupFileHash *setup_file_hash = NULL;
3647 TreeInfo *artwork_new = NULL;
3650 if (fileExists(filename))
3651 setup_file_hash = loadSetupFileHash(filename);
3653 if (setup_file_hash == NULL) // no config file -- look for artwork files
3656 DirectoryEntry *dir_entry;
3657 boolean valid_file_found = FALSE;
3659 if ((dir = openDirectory(directory_path)) != NULL)
3661 while ((dir_entry = readDirectory(dir)) != NULL)
3663 if (FileIsArtworkType(dir_entry->filename, type))
3665 valid_file_found = TRUE;
3671 closeDirectory(dir);
3674 if (!valid_file_found)
3676 #if DEBUG_NO_CONFIG_FILE
3677 if (!strEqual(directory_name, "."))
3678 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3681 free(directory_path);
3688 artwork_new = newTreeInfo();
3691 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3693 setTreeInfoToDefaults(artwork_new, type);
3695 artwork_new->subdir = getStringCopy(directory_name);
3697 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3699 // set all structure fields according to the token/value pairs
3701 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3702 setSetupInfo(levelinfo_tokens, i,
3703 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3706 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3707 setString(&artwork_new->name, artwork_new->subdir);
3709 if (artwork_new->identifier == NULL)
3710 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3712 if (artwork_new->name_sorting == NULL)
3713 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3716 if (node_parent == NULL) // top level group
3718 artwork_new->basepath = getStringCopy(base_directory);
3719 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3721 else // sub level group
3723 artwork_new->basepath = getStringCopy(node_parent->basepath);
3724 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3727 artwork_new->in_user_dir =
3728 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3730 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3732 if (setup_file_hash == NULL) // (after determining ".user_defined")
3734 if (strEqual(artwork_new->subdir, "."))
3736 if (artwork_new->user_defined)
3738 setString(&artwork_new->identifier, "private");
3739 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3743 setString(&artwork_new->identifier, "classic");
3744 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3747 setString(&artwork_new->class_desc,
3748 getLevelClassDescription(artwork_new));
3752 setString(&artwork_new->identifier, artwork_new->subdir);
3755 setString(&artwork_new->name, artwork_new->identifier);
3756 setString(&artwork_new->name_sorting, artwork_new->name);
3759 pushTreeInfo(node_first, artwork_new);
3761 freeSetupFileHash(setup_file_hash);
3763 free(directory_path);
3769 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3770 TreeInfo *node_parent,
3771 char *base_directory, int type)
3773 // ---------- 1st stage: process any artwork set zip files ----------
3775 ProcessZipFilesInDirectory(base_directory, type);
3777 // ---------- 2nd stage: check for artwork set directories ----------
3780 DirectoryEntry *dir_entry;
3781 boolean valid_entry_found = FALSE;
3783 if ((dir = openDirectory(base_directory)) == NULL)
3785 // display error if directory is main "options.graphics_directory" etc.
3786 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3787 Warn("cannot read directory '%s'", base_directory);
3792 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3794 char *directory_name = dir_entry->basename;
3795 char *directory_path = getPath2(base_directory, directory_name);
3797 // skip directory entries for current and parent directory
3798 if (strEqual(directory_name, ".") ||
3799 strEqual(directory_name, ".."))
3801 free(directory_path);
3806 // skip directory entries which are not a directory
3807 if (!dir_entry->is_directory) // not a directory
3809 free(directory_path);
3814 free(directory_path);
3816 // check if this directory contains artwork with or without config file
3817 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3819 directory_name, type);
3822 closeDirectory(dir);
3824 // check if this directory directly contains artwork itself
3825 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
3826 base_directory, ".",
3828 if (!valid_entry_found)
3829 Warn("cannot find any valid artwork in directory '%s'", base_directory);
3832 static TreeInfo *getDummyArtworkInfo(int type)
3834 // this is only needed when there is completely no artwork available
3835 TreeInfo *artwork_new = newTreeInfo();
3837 setTreeInfoToDefaults(artwork_new, type);
3839 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
3840 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
3841 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
3843 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
3844 setString(&artwork_new->name, UNDEFINED_FILENAME);
3845 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
3850 void SetCurrentArtwork(int type)
3852 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
3853 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
3854 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3855 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
3857 // set current artwork to artwork configured in setup menu
3858 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
3860 // if not found, set current artwork to default artwork
3861 if (*current_ptr == NULL)
3862 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
3864 // if not found, set current artwork to first artwork in tree
3865 if (*current_ptr == NULL)
3866 *current_ptr = getFirstValidTreeInfoEntry(first_node);
3869 void ChangeCurrentArtworkIfNeeded(int type)
3871 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
3872 char *setup_set = SETUP_ARTWORK_SET(setup, type);
3874 if (!strEqual(current_identifier, setup_set))
3875 SetCurrentArtwork(type);
3878 void LoadArtworkInfo(void)
3880 LoadArtworkInfoCache();
3882 DrawInitText("Looking for custom artwork", 120, FC_GREEN);
3884 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3885 options.graphics_directory,
3886 TREE_TYPE_GRAPHICS_DIR);
3887 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
3888 getUserGraphicsDir(),
3889 TREE_TYPE_GRAPHICS_DIR);
3891 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3892 options.sounds_directory,
3893 TREE_TYPE_SOUNDS_DIR);
3894 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
3896 TREE_TYPE_SOUNDS_DIR);
3898 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3899 options.music_directory,
3900 TREE_TYPE_MUSIC_DIR);
3901 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
3903 TREE_TYPE_MUSIC_DIR);
3905 if (artwork.gfx_first == NULL)
3906 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
3907 if (artwork.snd_first == NULL)
3908 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
3909 if (artwork.mus_first == NULL)
3910 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
3912 // before sorting, the first entries will be from the user directory
3913 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
3914 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
3915 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
3917 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
3918 artwork.snd_current_identifier = artwork.snd_current->identifier;
3919 artwork.mus_current_identifier = artwork.mus_current->identifier;
3921 #if ENABLE_UNUSED_CODE
3922 Debug("setup:LoadArtworkInfo", "graphics set == %s",
3923 artwork.gfx_current_identifier);
3924 Debug("setup:LoadArtworkInfo", "sounds set == %s",
3925 artwork.snd_current_identifier);
3926 Debug("setup:LoadArtworkInfo", "music set == %s",
3927 artwork.mus_current_identifier);
3930 sortTreeInfo(&artwork.gfx_first);
3931 sortTreeInfo(&artwork.snd_first);
3932 sortTreeInfo(&artwork.mus_first);
3934 #if ENABLE_UNUSED_CODE
3935 dumpTreeInfo(artwork.gfx_first, 0);
3936 dumpTreeInfo(artwork.snd_first, 0);
3937 dumpTreeInfo(artwork.mus_first, 0);
3941 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
3943 ArtworkDirTree *artwork_new = newTreeInfo();
3944 char *top_node_name = "standalone artwork";
3946 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
3948 artwork_new->level_group = TRUE;
3950 setString(&artwork_new->identifier, top_node_name);
3951 setString(&artwork_new->name, top_node_name);
3952 setString(&artwork_new->name_sorting, top_node_name);
3954 // create node to link back to current custom artwork directory
3955 createParentTreeInfoNode(artwork_new);
3957 // move existing custom artwork tree into newly created sub-tree
3958 artwork_new->node_group->next = *artwork_node;
3960 // change custom artwork tree to contain only newly created node
3961 *artwork_node = artwork_new;
3964 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
3965 ArtworkDirTree *node_parent,
3966 LevelDirTree *level_node,
3967 boolean empty_level_set_mode)
3969 int type = (*artwork_node)->type;
3971 // recursively check all level directories for artwork sub-directories
3975 boolean empty_level_set = (level_node->levels == 0);
3977 // check all tree entries for artwork, but skip parent link entries
3978 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
3980 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
3981 boolean cached = (artwork_new != NULL);
3985 pushTreeInfo(artwork_node, artwork_new);
3989 TreeInfo *topnode_last = *artwork_node;
3990 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
3991 ARTWORK_DIRECTORY(type));
3993 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
3995 if (topnode_last != *artwork_node) // check for newly added node
3997 artwork_new = *artwork_node;
3999 setString(&artwork_new->identifier, level_node->subdir);
4000 setString(&artwork_new->name, level_node->name);
4001 setString(&artwork_new->name_sorting, level_node->name_sorting);
4003 artwork_new->sort_priority = level_node->sort_priority;
4004 artwork_new->in_user_dir = level_node->in_user_dir;
4006 update_artworkinfo_cache = TRUE;
4012 // insert artwork info (from old cache or filesystem) into new cache
4013 if (artwork_new != NULL)
4014 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4017 DrawInitText(level_node->name, 150, FC_YELLOW);
4019 if (level_node->node_group != NULL)
4021 TreeInfo *artwork_new = newTreeInfo();
4024 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4026 setTreeInfoToDefaults(artwork_new, type);
4028 artwork_new->level_group = TRUE;
4030 setString(&artwork_new->identifier, level_node->subdir);
4032 if (node_parent == NULL) // check for top tree node
4034 char *top_node_name = (empty_level_set_mode ?
4035 "artwork for certain level sets" :
4036 "artwork included in level sets");
4038 setString(&artwork_new->name, top_node_name);
4039 setString(&artwork_new->name_sorting, top_node_name);
4043 setString(&artwork_new->name, level_node->name);
4044 setString(&artwork_new->name_sorting, level_node->name_sorting);
4047 pushTreeInfo(artwork_node, artwork_new);
4049 // create node to link back to current custom artwork directory
4050 createParentTreeInfoNode(artwork_new);
4052 // recursively step into sub-directory and look for more custom artwork
4053 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4054 level_node->node_group,
4055 empty_level_set_mode);
4057 // if sub-tree has no custom artwork at all, remove it
4058 if (artwork_new->node_group->next == NULL)
4059 removeTreeInfo(artwork_node);
4062 level_node = level_node->next;
4066 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4068 // move peviously loaded artwork tree into separate sub-tree
4069 MoveArtworkInfoIntoSubTree(artwork_node);
4071 // load artwork from level sets into separate sub-trees
4072 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4073 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4075 // add top tree node over all three separate sub-trees
4076 *artwork_node = createTopTreeInfoNode(*artwork_node);
4078 // set all parent links (back links) in complete artwork tree
4079 setTreeInfoParentNodes(*artwork_node, NULL);
4082 void LoadLevelArtworkInfo(void)
4084 print_timestamp_init("LoadLevelArtworkInfo");
4086 DrawInitText("Looking for custom level artwork", 120, FC_GREEN);
4088 print_timestamp_time("DrawTimeText");
4090 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4091 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4092 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4093 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4094 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4095 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4097 SaveArtworkInfoCache();
4099 print_timestamp_time("SaveArtworkInfoCache");
4101 // needed for reloading level artwork not known at ealier stage
4102 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4103 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4104 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4106 print_timestamp_time("getTreeInfoFromIdentifier");
4108 sortTreeInfo(&artwork.gfx_first);
4109 sortTreeInfo(&artwork.snd_first);
4110 sortTreeInfo(&artwork.mus_first);
4112 print_timestamp_time("sortTreeInfo");
4114 #if ENABLE_UNUSED_CODE
4115 dumpTreeInfo(artwork.gfx_first, 0);
4116 dumpTreeInfo(artwork.snd_first, 0);
4117 dumpTreeInfo(artwork.mus_first, 0);
4120 print_timestamp_done("LoadLevelArtworkInfo");
4123 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4124 char *tree_subdir_new, int type)
4126 if (tree_node_old == NULL)
4128 if (type == TREE_TYPE_LEVEL_DIR)
4130 // get level info tree node of personal user level set
4131 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4133 // this may happen if "setup.internal.create_user_levelset" is FALSE
4134 // or if file "levelinfo.conf" is missing in personal user level set
4135 if (tree_node_old == NULL)
4136 tree_node_old = leveldir_first->node_group;
4140 // get artwork info tree node of first artwork set
4141 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4145 if (tree_dir == NULL)
4146 tree_dir = TREE_USERDIR(type);
4148 if (tree_node_old == NULL ||
4150 tree_subdir_new == NULL) // should not happen
4153 int draw_deactivation_mask = GetDrawDeactivationMask();
4155 // override draw deactivation mask (temporarily disable drawing)
4156 SetDrawDeactivationMask(REDRAW_ALL);
4158 if (type == TREE_TYPE_LEVEL_DIR)
4160 // load new level set config and add it next to first user level set
4161 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4162 tree_node_old->node_parent,
4163 tree_dir, tree_subdir_new);
4167 // load new artwork set config and add it next to first artwork set
4168 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4169 tree_node_old->node_parent,
4170 tree_dir, tree_subdir_new, type);
4173 // set draw deactivation mask to previous value
4174 SetDrawDeactivationMask(draw_deactivation_mask);
4176 // get first node of level or artwork info tree
4177 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4179 // get tree info node of newly added level or artwork set
4180 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4183 if (tree_node_new == NULL) // should not happen
4186 // correct top link and parent node link of newly created tree node
4187 tree_node_new->node_top = tree_node_old->node_top;
4188 tree_node_new->node_parent = tree_node_old->node_parent;
4190 // sort tree info to adjust position of newly added tree set
4191 sortTreeInfo(tree_node_first);
4196 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4197 char *tree_subdir_new, int type)
4199 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4200 Fail("internal tree info structure corrupted -- aborting");
4203 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4205 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4208 char *getArtworkIdentifierForUserLevelSet(int type)
4210 char *classic_artwork_set = getClassicArtworkSet(type);
4212 // check for custom artwork configured in "levelinfo.conf"
4213 char *leveldir_artwork_set =
4214 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4215 boolean has_leveldir_artwork_set =
4216 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4217 classic_artwork_set));
4219 // check for custom artwork in sub-directory "graphics" etc.
4220 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4221 char *leveldir_identifier = leveldir_current->identifier;
4222 boolean has_artwork_subdir =
4223 (getTreeInfoFromIdentifier(artwork_first_node,
4224 leveldir_identifier) != NULL);
4226 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4227 has_artwork_subdir ? leveldir_identifier :
4228 classic_artwork_set);
4231 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4233 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4234 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4235 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4239 ti = getTreeInfoFromIdentifier(artwork_first_node,
4240 ARTWORK_DEFAULT_SUBDIR(type));
4242 Fail("cannot find default graphics -- should not happen");
4248 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4250 char *graphics_set =
4251 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4253 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4255 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4257 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4258 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4259 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4262 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4263 char *level_author, int num_levels)
4265 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4266 char *filename_tmp = getStringCat2(filename, ".tmp");
4268 FILE *file_tmp = NULL;
4269 char line[MAX_LINE_LEN];
4270 boolean success = FALSE;
4271 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4273 // update values in level directory tree
4275 if (level_name != NULL)
4276 setString(&leveldir->name, level_name);
4278 if (level_author != NULL)
4279 setString(&leveldir->author, level_author);
4281 if (num_levels != -1)
4282 leveldir->levels = num_levels;
4284 // update values that depend on other values
4286 setString(&leveldir->name_sorting, leveldir->name);
4288 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4290 // sort order of level sets may have changed
4291 sortTreeInfo(&leveldir_first);
4293 if ((file = fopen(filename, MODE_READ)) &&
4294 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4296 while (fgets(line, MAX_LINE_LEN, file))
4298 if (strPrefix(line, "name:") && level_name != NULL)
4299 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4300 else if (strPrefix(line, "author:") && level_author != NULL)
4301 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4302 else if (strPrefix(line, "levels:") && num_levels != -1)
4303 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4305 fputs(line, file_tmp);
4318 success = (rename(filename_tmp, filename) == 0);
4326 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4327 char *level_author, int num_levels,
4328 boolean use_artwork_set)
4330 LevelDirTree *level_info;
4335 // create user level sub-directory, if needed
4336 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4338 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4340 if (!(file = fopen(filename, MODE_WRITE)))
4342 Warn("cannot write level info file '%s'", filename);
4349 level_info = newTreeInfo();
4351 // always start with reliable default values
4352 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4354 setString(&level_info->name, level_name);
4355 setString(&level_info->author, level_author);
4356 level_info->levels = num_levels;
4357 level_info->first_level = 1;
4358 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4359 level_info->readonly = FALSE;
4361 if (use_artwork_set)
4363 level_info->graphics_set =
4364 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4365 level_info->sounds_set =
4366 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4367 level_info->music_set =
4368 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4371 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4373 fprintFileHeader(file, LEVELINFO_FILENAME);
4376 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4378 if (i == LEVELINFO_TOKEN_NAME ||
4379 i == LEVELINFO_TOKEN_AUTHOR ||
4380 i == LEVELINFO_TOKEN_LEVELS ||
4381 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4382 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4383 i == LEVELINFO_TOKEN_READONLY ||
4384 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4385 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4386 i == LEVELINFO_TOKEN_MUSIC_SET)))
4387 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4389 // just to make things nicer :)
4390 if (i == LEVELINFO_TOKEN_AUTHOR ||
4391 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4392 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4393 fprintf(file, "\n");
4396 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4400 SetFilePermissions(filename, PERMS_PRIVATE);
4402 freeTreeInfo(level_info);
4408 static void SaveUserLevelInfo(void)
4410 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4413 char *getSetupValue(int type, void *value)
4415 static char value_string[MAX_LINE_LEN];
4423 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4427 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4431 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4432 *(int *)value == FALSE ? "off" : "on"));
4436 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4439 case TYPE_YES_NO_AUTO:
4440 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4441 *(int *)value == FALSE ? "no" : "yes"));
4445 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4449 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4453 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4457 sprintf(value_string, "%d", *(int *)value);
4461 if (*(char **)value == NULL)
4464 strcpy(value_string, *(char **)value);
4468 sprintf(value_string, "player_%d", *(int *)value + 1);
4472 value_string[0] = '\0';
4476 if (type & TYPE_GHOSTED)
4477 strcpy(value_string, "n/a");
4479 return value_string;
4482 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4486 static char token_string[MAX_LINE_LEN];
4487 int token_type = token_info[token_nr].type;
4488 void *setup_value = token_info[token_nr].value;
4489 char *token_text = token_info[token_nr].text;
4490 char *value_string = getSetupValue(token_type, setup_value);
4492 // build complete token string
4493 sprintf(token_string, "%s%s", prefix, token_text);
4495 // build setup entry line
4496 line = getFormattedSetupEntry(token_string, value_string);
4498 if (token_type == TYPE_KEY_X11)
4500 Key key = *(Key *)setup_value;
4501 char *keyname = getKeyNameFromKey(key);
4503 // add comment, if useful
4504 if (!strEqual(keyname, "(undefined)") &&
4505 !strEqual(keyname, "(unknown)"))
4507 // add at least one whitespace
4509 for (i = strlen(line); i < token_comment_position; i++)
4513 strcat(line, keyname);
4520 static void InitLastPlayedLevels_ParentNode(void)
4522 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4523 LevelDirTree *leveldir_new = NULL;
4525 // check if parent node for last played levels already exists
4526 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4529 leveldir_new = newTreeInfo();
4531 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4533 leveldir_new->level_group = TRUE;
4534 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4536 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4537 setString(&leveldir_new->name, "<< (last played level sets)");
4538 setString(&leveldir_new->name_sorting, leveldir_new->name);
4540 pushTreeInfo(leveldir_top, leveldir_new);
4542 // create node to link back to current level directory
4543 createParentTreeInfoNode(leveldir_new);
4546 void UpdateLastPlayedLevels_TreeInfo(void)
4548 char **last_level_series = setup.level_setup.last_level_series;
4549 LevelDirTree *leveldir_last;
4550 TreeInfo **node_new = NULL;
4553 if (last_level_series[0] == NULL)
4556 InitLastPlayedLevels_ParentNode();
4558 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4559 TOKEN_STR_LAST_LEVEL_SERIES,
4560 TREE_NODE_TYPE_GROUP);
4561 if (leveldir_last == NULL)
4564 node_new = &leveldir_last->node_group->next;
4566 freeTreeInfo(*node_new);
4570 for (i = 0; last_level_series[i] != NULL; i++)
4572 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4573 last_level_series[i]);
4574 if (node_last == NULL)
4577 *node_new = getTreeInfoCopy(node_last); // copy complete node
4579 (*node_new)->node_top = &leveldir_first; // correct top node link
4580 (*node_new)->node_parent = leveldir_last; // correct parent node link
4582 (*node_new)->is_copy = TRUE; // mark entry as node copy
4584 (*node_new)->node_group = NULL;
4585 (*node_new)->next = NULL;
4587 (*node_new)->cl_first = -1; // force setting tree cursor
4589 node_new = &((*node_new)->next);
4593 static void UpdateLastPlayedLevels_List(void)
4595 char **last_level_series = setup.level_setup.last_level_series;
4596 int pos = MAX_LEVELDIR_HISTORY - 1;
4599 // search for potentially already existing entry in list of level sets
4600 for (i = 0; last_level_series[i] != NULL; i++)
4601 if (strEqual(last_level_series[i], leveldir_current->identifier))
4604 // move list of level sets one entry down (using potentially free entry)
4605 for (i = pos; i > 0; i--)
4606 setString(&last_level_series[i], last_level_series[i - 1]);
4608 // put last played level set at top position
4609 setString(&last_level_series[0], leveldir_current->identifier);
4612 void LoadLevelSetup_LastSeries(void)
4614 // --------------------------------------------------------------------------
4615 // ~/.<program>/levelsetup.conf
4616 // --------------------------------------------------------------------------
4618 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4619 SetupFileHash *level_setup_hash = NULL;
4623 // always start with reliable default values
4624 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4626 // start with empty history of last played level sets
4627 setString(&setup.level_setup.last_level_series[0], NULL);
4629 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4631 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4633 if (leveldir_current == NULL)
4634 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4637 if ((level_setup_hash = loadSetupFileHash(filename)))
4639 char *last_level_series =
4640 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4642 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4644 if (leveldir_current == NULL)
4645 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4647 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4649 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4650 LevelDirTree *leveldir_last;
4652 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4654 last_level_series = getHashEntry(level_setup_hash, token);
4656 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4658 if (leveldir_last != NULL)
4659 setString(&setup.level_setup.last_level_series[pos++],
4663 setString(&setup.level_setup.last_level_series[pos], NULL);
4665 freeSetupFileHash(level_setup_hash);
4669 Debug("setup", "using default setup values");
4675 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4677 // --------------------------------------------------------------------------
4678 // ~/.<program>/levelsetup.conf
4679 // --------------------------------------------------------------------------
4681 // check if the current level directory structure is available at this point
4682 if (leveldir_current == NULL)
4685 char **last_level_series = setup.level_setup.last_level_series;
4686 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4690 InitUserDataDirectory();
4692 UpdateLastPlayedLevels_List();
4694 if (!(file = fopen(filename, MODE_WRITE)))
4696 Warn("cannot write setup file '%s'", filename);
4703 fprintFileHeader(file, LEVELSETUP_FILENAME);
4705 if (deactivate_last_level_series)
4706 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4708 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4709 leveldir_current->identifier));
4711 for (i = 0; last_level_series[i] != NULL; i++)
4713 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4715 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4717 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4722 SetFilePermissions(filename, PERMS_PRIVATE);
4727 void SaveLevelSetup_LastSeries(void)
4729 SaveLevelSetup_LastSeries_Ext(FALSE);
4732 void SaveLevelSetup_LastSeries_Deactivate(void)
4734 SaveLevelSetup_LastSeries_Ext(TRUE);
4737 static void checkSeriesInfo(void)
4739 static char *level_directory = NULL;
4742 DirectoryEntry *dir_entry;
4745 checked_free(level_directory);
4747 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4749 level_directory = getPath2((leveldir_current->in_user_dir ?
4750 getUserLevelDir(NULL) :
4751 options.level_directory),
4752 leveldir_current->fullpath);
4754 if ((dir = openDirectory(level_directory)) == NULL)
4756 Warn("cannot read level directory '%s'", level_directory);
4762 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4764 if (strlen(dir_entry->basename) > 4 &&
4765 dir_entry->basename[3] == '.' &&
4766 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4768 char levelnum_str[4];
4771 strncpy(levelnum_str, dir_entry->basename, 3);
4772 levelnum_str[3] = '\0';
4774 levelnum_value = atoi(levelnum_str);
4776 if (levelnum_value < leveldir_current->first_level)
4778 Warn("additional level %d found", levelnum_value);
4780 leveldir_current->first_level = levelnum_value;
4782 else if (levelnum_value > leveldir_current->last_level)
4784 Warn("additional level %d found", levelnum_value);
4786 leveldir_current->last_level = levelnum_value;
4792 closeDirectory(dir);
4795 void LoadLevelSetup_SeriesInfo(void)
4798 SetupFileHash *level_setup_hash = NULL;
4799 char *level_subdir = leveldir_current->subdir;
4802 // always start with reliable default values
4803 level_nr = leveldir_current->first_level;
4805 for (i = 0; i < MAX_LEVELS; i++)
4807 LevelStats_setPlayed(i, 0);
4808 LevelStats_setSolved(i, 0);
4813 // --------------------------------------------------------------------------
4814 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4815 // --------------------------------------------------------------------------
4817 level_subdir = leveldir_current->subdir;
4819 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4821 if ((level_setup_hash = loadSetupFileHash(filename)))
4825 // get last played level in this level set
4827 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
4831 level_nr = atoi(token_value);
4833 if (level_nr < leveldir_current->first_level)
4834 level_nr = leveldir_current->first_level;
4835 if (level_nr > leveldir_current->last_level)
4836 level_nr = leveldir_current->last_level;
4839 // get handicap level in this level set
4841 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
4845 int level_nr = atoi(token_value);
4847 if (level_nr < leveldir_current->first_level)
4848 level_nr = leveldir_current->first_level;
4849 if (level_nr > leveldir_current->last_level + 1)
4850 level_nr = leveldir_current->last_level;
4852 if (leveldir_current->user_defined || !leveldir_current->handicap)
4853 level_nr = leveldir_current->last_level;
4855 leveldir_current->handicap_level = level_nr;
4858 // get number of played and solved levels in this level set
4860 BEGIN_HASH_ITERATION(level_setup_hash, itr)
4862 char *token = HASH_ITERATION_TOKEN(itr);
4863 char *value = HASH_ITERATION_VALUE(itr);
4865 if (strlen(token) == 3 &&
4866 token[0] >= '0' && token[0] <= '9' &&
4867 token[1] >= '0' && token[1] <= '9' &&
4868 token[2] >= '0' && token[2] <= '9')
4870 int level_nr = atoi(token);
4873 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
4875 value = strchr(value, ' ');
4878 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
4881 END_HASH_ITERATION(hash, itr)
4883 freeSetupFileHash(level_setup_hash);
4887 Debug("setup", "using default setup values");
4893 void SaveLevelSetup_SeriesInfo(void)
4896 char *level_subdir = leveldir_current->subdir;
4897 char *level_nr_str = int2str(level_nr, 0);
4898 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
4902 // --------------------------------------------------------------------------
4903 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
4904 // --------------------------------------------------------------------------
4906 InitLevelSetupDirectory(level_subdir);
4908 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
4910 if (!(file = fopen(filename, MODE_WRITE)))
4912 Warn("cannot write setup file '%s'", filename);
4919 fprintFileHeader(file, LEVELSETUP_FILENAME);
4921 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
4923 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
4924 handicap_level_str));
4926 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
4929 if (LevelStats_getPlayed(i) > 0 ||
4930 LevelStats_getSolved(i) > 0)
4935 sprintf(token, "%03d", i);
4936 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
4938 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
4944 SetFilePermissions(filename, PERMS_PRIVATE);
4949 int LevelStats_getPlayed(int nr)
4951 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
4954 int LevelStats_getSolved(int nr)
4956 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
4959 void LevelStats_setPlayed(int nr, int value)
4961 if (nr >= 0 && nr < MAX_LEVELS)
4962 level_stats[nr].played = value;
4965 void LevelStats_setSolved(int nr, int value)
4967 if (nr >= 0 && nr < MAX_LEVELS)
4968 level_stats[nr].solved = value;
4971 void LevelStats_incPlayed(int nr)
4973 if (nr >= 0 && nr < MAX_LEVELS)
4974 level_stats[nr].played++;
4977 void LevelStats_incSolved(int nr)
4979 if (nr >= 0 && nr < MAX_LEVELS)
4980 level_stats[nr].solved++;
4983 void LoadUserSetup(void)
4985 // --------------------------------------------------------------------------
4986 // ~/.<program>/usersetup.conf
4987 // --------------------------------------------------------------------------
4989 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
4990 SetupFileHash *user_setup_hash = NULL;
4992 // always start with reliable default values
4995 if ((user_setup_hash = loadSetupFileHash(filename)))
4999 // get last selected user number
5000 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5003 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5005 freeSetupFileHash(user_setup_hash);
5009 Debug("setup", "using default setup values");
5015 void SaveUserSetup(void)
5017 // --------------------------------------------------------------------------
5018 // ~/.<program>/usersetup.conf
5019 // --------------------------------------------------------------------------
5021 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5024 InitMainUserDataDirectory();
5026 if (!(file = fopen(filename, MODE_WRITE)))
5028 Warn("cannot write setup file '%s'", filename);
5035 fprintFileHeader(file, USERSETUP_FILENAME);
5037 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5041 SetFilePermissions(filename, PERMS_PRIVATE);