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 *getCacheDir(void)
93 static char *cache_dir = NULL;
95 if (cache_dir == NULL)
96 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
101 static char *getScoreDir(char *level_subdir)
103 static char *score_dir = NULL;
104 static char *score_level_dir = NULL;
105 char *score_subdir = SCORES_DIRECTORY;
107 if (score_dir == NULL)
108 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
110 if (level_subdir != NULL)
112 checked_free(score_level_dir);
114 score_level_dir = getPath2(score_dir, level_subdir);
116 return score_level_dir;
122 static char *getScoreCacheDir(char *level_subdir)
124 static char *score_dir = NULL;
125 static char *score_level_dir = NULL;
126 char *score_subdir = SCORES_DIRECTORY;
128 if (score_dir == NULL)
129 score_dir = getPath2(getCacheDir(), score_subdir);
131 if (level_subdir != NULL)
133 checked_free(score_level_dir);
135 score_level_dir = getPath2(score_dir, level_subdir);
137 return score_level_dir;
143 static char *getScoreTapeDir(char *level_subdir, int nr)
145 static char *score_tape_dir = NULL;
146 char tape_subdir[MAX_FILENAME_LEN];
148 checked_free(score_tape_dir);
150 sprintf(tape_subdir, "%03d", nr);
151 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
153 return score_tape_dir;
156 static char *getUserSubdir(int nr)
158 static char user_subdir[16] = { 0 };
160 sprintf(user_subdir, "%03d", nr);
165 static char *getUserDir(int nr)
167 static char *user_dir = NULL;
168 char *main_data_dir = getMainUserGameDataDir();
169 char *users_subdir = USERS_DIRECTORY;
170 char *user_subdir = getUserSubdir(nr);
172 checked_free(user_dir);
175 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
177 user_dir = getPath2(main_data_dir, users_subdir);
182 static char *getLevelSetupDir(char *level_subdir)
184 static char *levelsetup_dir = NULL;
185 char *data_dir = getUserGameDataDir();
186 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
188 checked_free(levelsetup_dir);
190 if (level_subdir != NULL)
191 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
193 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
195 return levelsetup_dir;
198 static char *getNetworkDir(void)
200 static char *network_dir = NULL;
202 if (network_dir == NULL)
203 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
208 char *getLevelDirFromTreeInfo(TreeInfo *node)
210 static char *level_dir = NULL;
213 return options.level_directory;
215 checked_free(level_dir);
217 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
218 options.level_directory), node->fullpath);
223 char *getUserLevelDir(char *level_subdir)
225 static char *userlevel_dir = NULL;
226 char *data_dir = getMainUserGameDataDir();
227 char *userlevel_subdir = LEVELS_DIRECTORY;
229 checked_free(userlevel_dir);
231 if (level_subdir != NULL)
232 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
234 userlevel_dir = getPath2(data_dir, userlevel_subdir);
236 return userlevel_dir;
239 char *getNetworkLevelDir(char *level_subdir)
241 static char *network_level_dir = NULL;
242 char *data_dir = getNetworkDir();
243 char *networklevel_subdir = LEVELS_DIRECTORY;
245 checked_free(network_level_dir);
247 if (level_subdir != NULL)
248 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
250 network_level_dir = getPath2(data_dir, networklevel_subdir);
252 return network_level_dir;
255 char *getCurrentLevelDir(void)
257 return getLevelDirFromTreeInfo(leveldir_current);
260 char *getNewUserLevelSubdir(void)
262 static char *new_level_subdir = NULL;
263 char *subdir_prefix = getLoginName();
264 char subdir_suffix[10];
265 int max_suffix_number = 1000;
268 while (++i < max_suffix_number)
270 sprintf(subdir_suffix, "_%d", i);
272 checked_free(new_level_subdir);
273 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
275 if (!directoryExists(getUserLevelDir(new_level_subdir)))
279 return new_level_subdir;
282 char *getTapeDir(char *level_subdir)
284 static char *tape_dir = NULL;
285 char *data_dir = getUserGameDataDir();
286 char *tape_subdir = TAPES_DIRECTORY;
288 checked_free(tape_dir);
290 if (level_subdir != NULL)
291 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
293 tape_dir = getPath2(data_dir, tape_subdir);
298 static char *getSolutionTapeDir(void)
300 static char *tape_dir = NULL;
301 char *data_dir = getCurrentLevelDir();
302 char *tape_subdir = TAPES_DIRECTORY;
304 checked_free(tape_dir);
306 tape_dir = getPath2(data_dir, tape_subdir);
311 static char *getDefaultGraphicsDir(char *graphics_subdir)
313 static char *graphics_dir = NULL;
315 if (graphics_subdir == NULL)
316 return options.graphics_directory;
318 checked_free(graphics_dir);
320 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
325 static char *getDefaultSoundsDir(char *sounds_subdir)
327 static char *sounds_dir = NULL;
329 if (sounds_subdir == NULL)
330 return options.sounds_directory;
332 checked_free(sounds_dir);
334 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
339 static char *getDefaultMusicDir(char *music_subdir)
341 static char *music_dir = NULL;
343 if (music_subdir == NULL)
344 return options.music_directory;
346 checked_free(music_dir);
348 music_dir = getPath2(options.music_directory, music_subdir);
353 static char *getClassicArtworkSet(int type)
355 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
356 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
357 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
360 static char *getClassicArtworkDir(int type)
362 return (type == TREE_TYPE_GRAPHICS_DIR ?
363 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
364 type == TREE_TYPE_SOUNDS_DIR ?
365 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
366 type == TREE_TYPE_MUSIC_DIR ?
367 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
370 char *getUserGraphicsDir(void)
372 static char *usergraphics_dir = NULL;
374 if (usergraphics_dir == NULL)
375 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
377 return usergraphics_dir;
380 char *getUserSoundsDir(void)
382 static char *usersounds_dir = NULL;
384 if (usersounds_dir == NULL)
385 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
387 return usersounds_dir;
390 char *getUserMusicDir(void)
392 static char *usermusic_dir = NULL;
394 if (usermusic_dir == NULL)
395 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
397 return usermusic_dir;
400 static char *getSetupArtworkDir(TreeInfo *ti)
402 static char *artwork_dir = NULL;
407 checked_free(artwork_dir);
409 artwork_dir = getPath2(ti->basepath, ti->fullpath);
414 char *setLevelArtworkDir(TreeInfo *ti)
416 char **artwork_path_ptr, **artwork_set_ptr;
417 TreeInfo *level_artwork;
419 if (ti == NULL || leveldir_current == NULL)
422 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
423 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
425 checked_free(*artwork_path_ptr);
427 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
429 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
434 No (or non-existing) artwork configured in "levelinfo.conf". This would
435 normally result in using the artwork configured in the setup menu. But
436 if an artwork subdirectory exists (which might contain custom artwork
437 or an artwork configuration file), this level artwork must be treated
438 as relative to the default "classic" artwork, not to the artwork that
439 is currently configured in the setup menu.
441 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
442 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
443 the real "classic" artwork from the original R'n'D (like "gfx_classic").
446 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
448 checked_free(*artwork_set_ptr);
450 if (directoryExists(dir))
452 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
453 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
457 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
458 *artwork_set_ptr = NULL;
464 return *artwork_set_ptr;
467 static char *getLevelArtworkSet(int type)
469 if (leveldir_current == NULL)
472 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
475 static char *getLevelArtworkDir(int type)
477 if (leveldir_current == NULL)
478 return UNDEFINED_FILENAME;
480 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
483 char *getProgramMainDataPath(char *command_filename, char *base_path)
485 // check if the program's main data base directory is configured
486 if (!strEqual(base_path, "."))
487 return getStringCopy(base_path);
489 /* if the program is configured to start from current directory (default),
490 determine program package directory from program binary (some versions
491 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
492 set the current working directory to the program package directory) */
493 char *main_data_path = getBasePath(command_filename);
495 #if defined(PLATFORM_MACOSX)
496 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
498 char *main_data_path_old = main_data_path;
500 // cut relative path to Mac OS X application binary directory from path
501 main_data_path[strlen(main_data_path) -
502 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
504 // cut trailing path separator from path (but not if path is root directory)
505 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
506 main_data_path[strlen(main_data_path) - 1] = '\0';
508 // replace empty path with current directory
509 if (strEqual(main_data_path, ""))
510 main_data_path = ".";
512 // add relative path to Mac OS X application resources directory to path
513 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
515 free(main_data_path_old);
519 return main_data_path;
522 char *getProgramConfigFilename(char *command_filename)
524 static char *config_filename_1 = NULL;
525 static char *config_filename_2 = NULL;
526 static char *config_filename_3 = NULL;
527 static boolean initialized = FALSE;
531 char *command_filename_1 = getStringCopy(command_filename);
533 // strip trailing executable suffix from command filename
534 if (strSuffix(command_filename_1, ".exe"))
535 command_filename_1[strlen(command_filename_1) - 4] = '\0';
537 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
538 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
540 char *command_basepath = getBasePath(command_filename);
541 char *command_basename = getBaseNameNoSuffix(command_filename);
542 char *command_filename_2 = getPath2(command_basepath, command_basename);
544 config_filename_1 = getStringCat2(command_filename_1, ".conf");
545 config_filename_2 = getStringCat2(command_filename_2, ".conf");
546 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
548 checked_free(base_path);
549 checked_free(conf_directory);
551 checked_free(command_basepath);
552 checked_free(command_basename);
554 checked_free(command_filename_1);
555 checked_free(command_filename_2);
560 // 1st try: look for config file that exactly matches the binary filename
561 if (fileExists(config_filename_1))
562 return config_filename_1;
564 // 2nd try: look for config file that matches binary filename without suffix
565 if (fileExists(config_filename_2))
566 return config_filename_2;
568 // 3rd try: return setup config filename in global program config directory
569 return config_filename_3;
572 char *getTapeFilename(int nr)
574 static char *filename = NULL;
575 char basename[MAX_FILENAME_LEN];
577 checked_free(filename);
579 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
580 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
585 char *getTemporaryTapeFilename(void)
587 static char *filename = NULL;
588 char basename[MAX_FILENAME_LEN];
590 checked_free(filename);
592 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
593 filename = getPath2(getTapeDir(NULL), basename);
598 char *getDefaultSolutionTapeFilename(int nr)
600 static char *filename = NULL;
601 char basename[MAX_FILENAME_LEN];
603 checked_free(filename);
605 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
606 filename = getPath2(getSolutionTapeDir(), basename);
611 char *getSokobanSolutionTapeFilename(int nr)
613 static char *filename = NULL;
614 char basename[MAX_FILENAME_LEN];
616 checked_free(filename);
618 sprintf(basename, "%03d.sln", nr);
619 filename = getPath2(getSolutionTapeDir(), basename);
624 char *getSolutionTapeFilename(int nr)
626 char *filename = getDefaultSolutionTapeFilename(nr);
628 if (!fileExists(filename))
630 char *filename2 = getSokobanSolutionTapeFilename(nr);
632 if (fileExists(filename2))
639 char *getScoreFilename(int nr)
641 static char *filename = NULL;
642 char basename[MAX_FILENAME_LEN];
644 checked_free(filename);
646 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
648 // used instead of "leveldir_current->subdir" (for network games)
649 filename = getPath2(getScoreDir(levelset.identifier), basename);
654 char *getScoreCacheFilename(int nr)
656 static char *filename = NULL;
657 char basename[MAX_FILENAME_LEN];
659 checked_free(filename);
661 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
663 // used instead of "leveldir_current->subdir" (for network games)
664 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
669 char *getScoreTapeBasename(char *name)
671 static char basename[MAX_FILENAME_LEN];
672 char basename_raw[MAX_FILENAME_LEN];
675 sprintf(timestamp, "%s", getCurrentTimestamp());
676 sprintf(basename_raw, "%s-%s", timestamp, name);
677 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
682 char *getScoreTapeFilename(char *basename_no_ext, int nr)
684 static char *filename = NULL;
685 char basename[MAX_FILENAME_LEN];
687 checked_free(filename);
689 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
691 // used instead of "leveldir_current->subdir" (for network games)
692 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
697 char *getSetupFilename(void)
699 static char *filename = NULL;
701 checked_free(filename);
703 filename = getPath2(getSetupDir(), SETUP_FILENAME);
708 char *getDefaultSetupFilename(void)
710 return program.config_filename;
713 char *getEditorSetupFilename(void)
715 static char *filename = NULL;
717 checked_free(filename);
718 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
720 if (fileExists(filename))
723 checked_free(filename);
724 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
729 char *getHelpAnimFilename(void)
731 static char *filename = NULL;
733 checked_free(filename);
735 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
740 char *getHelpTextFilename(void)
742 static char *filename = NULL;
744 checked_free(filename);
746 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
751 char *getLevelSetInfoFilename(void)
753 static char *filename = NULL;
768 for (i = 0; basenames[i] != NULL; i++)
770 checked_free(filename);
771 filename = getPath2(getCurrentLevelDir(), basenames[i]);
773 if (fileExists(filename))
780 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
782 static char basename[32];
784 sprintf(basename, "%s_%d.txt",
785 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
790 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
792 static char *filename = NULL;
794 boolean skip_setup_artwork = FALSE;
796 checked_free(filename);
798 basename = getLevelSetTitleMessageBasename(nr, initial);
800 if (!gfx.override_level_graphics)
802 // 1st try: look for special artwork in current level series directory
803 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
804 if (fileExists(filename))
809 // 2nd try: look for message file in current level set directory
810 filename = getPath2(getCurrentLevelDir(), basename);
811 if (fileExists(filename))
816 // check if there is special artwork configured in level series config
817 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
819 // 3rd try: look for special artwork configured in level series config
820 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
821 if (fileExists(filename))
826 // take missing artwork configured in level set config from default
827 skip_setup_artwork = TRUE;
831 if (!skip_setup_artwork)
833 // 4th try: look for special artwork in configured artwork directory
834 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
835 if (fileExists(filename))
841 // 5th try: look for default artwork in new default artwork directory
842 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
843 if (fileExists(filename))
848 // 6th try: look for default artwork in old default artwork directory
849 filename = getPath2(options.graphics_directory, basename);
850 if (fileExists(filename))
853 return NULL; // cannot find specified artwork file anywhere
856 static char *getCreditsBasename(int nr)
858 static char basename[32];
860 sprintf(basename, "credits_%d.txt", nr + 1);
865 char *getCreditsFilename(int nr, boolean global)
867 char *basename = getCreditsBasename(nr);
868 char *basepath = NULL;
869 static char *credits_subdir = NULL;
870 static char *filename = NULL;
872 if (credits_subdir == NULL)
873 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
875 checked_free(filename);
877 // look for credits file in the game's base or current level set directory
878 basepath = (global ? options.base_directory : getCurrentLevelDir());
880 filename = getPath3(basepath, credits_subdir, basename);
881 if (fileExists(filename))
884 return NULL; // cannot find credits file
887 static char *getProgramInfoBasename(int nr)
889 static char basename[32];
891 sprintf(basename, "program_%d.txt", nr + 1);
896 char *getProgramInfoFilename(int nr)
898 char *basename = getProgramInfoBasename(nr);
899 static char *info_subdir = NULL;
900 static char *filename = NULL;
902 if (info_subdir == NULL)
903 info_subdir = getPath2(DOCS_DIRECTORY, INFO_DIRECTORY);
905 checked_free(filename);
907 // look for program info file in the game's base directory
908 filename = getPath3(options.base_directory, info_subdir, basename);
909 if (fileExists(filename))
912 return NULL; // cannot find program info file
915 static char *getCorrectedArtworkBasename(char *basename)
920 char *getCustomImageFilename(char *basename)
922 static char *filename = NULL;
923 boolean skip_setup_artwork = FALSE;
925 checked_free(filename);
927 basename = getCorrectedArtworkBasename(basename);
929 if (!gfx.override_level_graphics)
931 // 1st try: look for special artwork in current level series directory
932 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
933 if (fileExists(filename))
938 // check if there is special artwork configured in level series config
939 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
941 // 2nd try: look for special artwork configured in level series config
942 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
943 if (fileExists(filename))
948 // take missing artwork configured in level set config from default
949 skip_setup_artwork = TRUE;
953 if (!skip_setup_artwork)
955 // 3rd try: look for special artwork in configured artwork directory
956 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
957 if (fileExists(filename))
963 // 4th try: look for default artwork in new default artwork directory
964 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
965 if (fileExists(filename))
970 // 5th try: look for default artwork in old default artwork directory
971 filename = getImg2(options.graphics_directory, basename);
972 if (fileExists(filename))
975 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
979 Warn("cannot find artwork file '%s' (using fallback)", basename);
981 // 6th try: look for fallback artwork in old default artwork directory
982 // (needed to prevent errors when trying to access unused artwork files)
983 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
984 if (fileExists(filename))
988 return NULL; // cannot find specified artwork file anywhere
991 char *getCustomSoundFilename(char *basename)
993 static char *filename = NULL;
994 boolean skip_setup_artwork = FALSE;
996 checked_free(filename);
998 basename = getCorrectedArtworkBasename(basename);
1000 if (!gfx.override_level_sounds)
1002 // 1st try: look for special artwork in current level series directory
1003 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1004 if (fileExists(filename))
1009 // check if there is special artwork configured in level series config
1010 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1012 // 2nd try: look for special artwork configured in level series config
1013 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1014 if (fileExists(filename))
1019 // take missing artwork configured in level set config from default
1020 skip_setup_artwork = TRUE;
1024 if (!skip_setup_artwork)
1026 // 3rd try: look for special artwork in configured artwork directory
1027 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1028 if (fileExists(filename))
1034 // 4th try: look for default artwork in new default artwork directory
1035 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1036 if (fileExists(filename))
1041 // 5th try: look for default artwork in old default artwork directory
1042 filename = getPath2(options.sounds_directory, basename);
1043 if (fileExists(filename))
1046 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1050 Warn("cannot find artwork file '%s' (using fallback)", basename);
1052 // 6th try: look for fallback artwork in old default artwork directory
1053 // (needed to prevent errors when trying to access unused artwork files)
1054 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1055 if (fileExists(filename))
1059 return NULL; // cannot find specified artwork file anywhere
1062 char *getCustomMusicFilename(char *basename)
1064 static char *filename = NULL;
1065 boolean skip_setup_artwork = FALSE;
1067 checked_free(filename);
1069 basename = getCorrectedArtworkBasename(basename);
1071 if (!gfx.override_level_music)
1073 // 1st try: look for special artwork in current level series directory
1074 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1075 if (fileExists(filename))
1080 // check if there is special artwork configured in level series config
1081 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1083 // 2nd try: look for special artwork configured in level series config
1084 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1085 if (fileExists(filename))
1090 // take missing artwork configured in level set config from default
1091 skip_setup_artwork = TRUE;
1095 if (!skip_setup_artwork)
1097 // 3rd try: look for special artwork in configured artwork directory
1098 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1099 if (fileExists(filename))
1105 // 4th try: look for default artwork in new default artwork directory
1106 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1107 if (fileExists(filename))
1112 // 5th try: look for default artwork in old default artwork directory
1113 filename = getPath2(options.music_directory, basename);
1114 if (fileExists(filename))
1117 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1121 Warn("cannot find artwork file '%s' (using fallback)", basename);
1123 // 6th try: look for fallback artwork in old default artwork directory
1124 // (needed to prevent errors when trying to access unused artwork files)
1125 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1126 if (fileExists(filename))
1130 return NULL; // cannot find specified artwork file anywhere
1133 char *getCustomArtworkFilename(char *basename, int type)
1135 if (type == ARTWORK_TYPE_GRAPHICS)
1136 return getCustomImageFilename(basename);
1137 else if (type == ARTWORK_TYPE_SOUNDS)
1138 return getCustomSoundFilename(basename);
1139 else if (type == ARTWORK_TYPE_MUSIC)
1140 return getCustomMusicFilename(basename);
1142 return UNDEFINED_FILENAME;
1145 char *getCustomArtworkConfigFilename(int type)
1147 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1150 char *getCustomArtworkLevelConfigFilename(int type)
1152 static char *filename = NULL;
1154 checked_free(filename);
1156 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1161 char *getCustomMusicDirectory(void)
1163 static char *directory = NULL;
1164 boolean skip_setup_artwork = FALSE;
1166 checked_free(directory);
1168 if (!gfx.override_level_music)
1170 // 1st try: look for special artwork in current level series directory
1171 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1172 if (directoryExists(directory))
1177 // check if there is special artwork configured in level series config
1178 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1180 // 2nd try: look for special artwork configured in level series config
1181 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1182 if (directoryExists(directory))
1187 // take missing artwork configured in level set config from default
1188 skip_setup_artwork = TRUE;
1192 if (!skip_setup_artwork)
1194 // 3rd try: look for special artwork in configured artwork directory
1195 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1196 if (directoryExists(directory))
1202 // 4th try: look for default artwork in new default artwork directory
1203 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1204 if (directoryExists(directory))
1209 // 5th try: look for default artwork in old default artwork directory
1210 directory = getStringCopy(options.music_directory);
1211 if (directoryExists(directory))
1214 return NULL; // cannot find specified artwork file anywhere
1217 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1219 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1221 touchFile(filename);
1223 checked_free(filename);
1226 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1228 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1232 checked_free(filename);
1235 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1237 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1238 boolean success = fileExists(filename);
1240 checked_free(filename);
1245 void InitTapeDirectory(char *level_subdir)
1247 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1249 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1250 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1251 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1254 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1257 void InitScoreDirectory(char *level_subdir)
1259 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1260 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1261 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1264 void InitScoreCacheDirectory(char *level_subdir)
1266 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1267 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1268 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1269 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1272 void InitScoreTapeDirectory(char *level_subdir, int nr)
1274 InitScoreDirectory(level_subdir);
1276 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1279 static void SaveUserLevelInfo(void);
1281 void InitUserLevelDirectory(char *level_subdir)
1283 if (!directoryExists(getUserLevelDir(level_subdir)))
1285 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1286 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1287 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1289 if (setup.internal.create_user_levelset)
1290 SaveUserLevelInfo();
1294 void InitNetworkLevelDirectory(char *level_subdir)
1296 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1298 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1299 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1300 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1301 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1305 void InitLevelSetupDirectory(char *level_subdir)
1307 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1308 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1309 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1312 static void InitCacheDirectory(void)
1314 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1315 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1319 // ----------------------------------------------------------------------------
1320 // some functions to handle lists of level and artwork directories
1321 // ----------------------------------------------------------------------------
1323 TreeInfo *newTreeInfo(void)
1325 return checked_calloc(sizeof(TreeInfo));
1328 TreeInfo *newTreeInfo_setDefaults(int type)
1330 TreeInfo *ti = newTreeInfo();
1332 setTreeInfoToDefaults(ti, type);
1337 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1339 node_new->next = *node_first;
1340 *node_first = node_new;
1343 void removeTreeInfo(TreeInfo **node_first)
1345 TreeInfo *node_old = *node_first;
1347 *node_first = node_old->next;
1348 node_old->next = NULL;
1350 freeTreeInfo(node_old);
1353 int numTreeInfo(TreeInfo *node)
1366 boolean validLevelSeries(TreeInfo *node)
1368 // in a number of cases, tree node is no valid level set
1369 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1375 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1377 if (validLevelSeries(node))
1379 else if (node->is_copy)
1380 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1382 return getFirstValidTreeInfoEntry(default_node);
1385 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1390 if (node->node_group) // enter node group (step down into tree)
1391 return getFirstValidTreeInfoEntry(node->node_group);
1393 if (node->parent_link) // skip first node (back link) of node group
1394 get_next_node = TRUE;
1396 if (!get_next_node) // get current regular tree node
1399 // get next regular tree node, or step up until one is found
1400 while (node->next == NULL && node->node_parent != NULL)
1401 node = node->node_parent;
1403 return getFirstValidTreeInfoEntry(node->next);
1406 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1408 return getValidTreeInfoEntryExt(node, FALSE);
1411 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1413 return getValidTreeInfoEntryExt(node, TRUE);
1416 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1421 if (node->node_parent == NULL) // top level group
1422 return *node->node_top;
1423 else // sub level group
1424 return node->node_parent->node_group;
1427 int numTreeInfoInGroup(TreeInfo *node)
1429 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1432 int getPosFromTreeInfo(TreeInfo *node)
1434 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1439 if (node_cmp == node)
1443 node_cmp = node_cmp->next;
1449 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1451 TreeInfo *node_default = node;
1463 return node_default;
1466 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1467 int node_type_wanted)
1469 if (identifier == NULL)
1474 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1475 strEqual(identifier, node->identifier))
1478 if (node->node_group)
1480 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1493 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1495 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1498 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1499 TreeInfo *node, boolean skip_sets_without_levels)
1506 if (!node->parent_link && !node->level_group &&
1507 skip_sets_without_levels && node->levels == 0)
1508 return cloneTreeNode(node_top, node_parent, node->next,
1509 skip_sets_without_levels);
1511 node_new = getTreeInfoCopy(node); // copy complete node
1513 node_new->node_top = node_top; // correct top node link
1514 node_new->node_parent = node_parent; // correct parent node link
1516 if (node->level_group)
1517 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1518 skip_sets_without_levels);
1520 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1521 skip_sets_without_levels);
1526 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1528 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1530 *ti_new = ti_cloned;
1533 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1535 boolean settings_changed = FALSE;
1539 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1540 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1541 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1542 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1543 char *graphics_set = NULL;
1545 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1546 graphics_set = node->graphics_set_ecs;
1548 if (node->graphics_set_aga && (want_aga || has_only_aga))
1549 graphics_set = node->graphics_set_aga;
1551 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1553 setString(&node->graphics_set, graphics_set);
1554 settings_changed = TRUE;
1557 if (node->node_group != NULL)
1558 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1563 return settings_changed;
1566 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1568 boolean settings_changed = FALSE;
1572 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1573 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1574 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1575 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1576 char *sounds_set = NULL;
1578 if (node->sounds_set_default && (want_default || has_only_default))
1579 sounds_set = node->sounds_set_default;
1581 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1582 sounds_set = node->sounds_set_lowpass;
1584 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1586 setString(&node->sounds_set, sounds_set);
1587 settings_changed = TRUE;
1590 if (node->node_group != NULL)
1591 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1596 return settings_changed;
1599 int dumpTreeInfo(TreeInfo *node, int depth)
1601 char bullet_list[] = { '-', '*', 'o' };
1602 int num_leaf_nodes = 0;
1606 Debug("tree", "Dumping TreeInfo:");
1610 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1612 for (i = 0; i < depth * 2; i++)
1613 DebugContinued("", " ");
1615 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1616 bullet, node->name, node->identifier,
1617 (node->node_parent ? node->node_parent->identifier : "-"),
1618 (node->node_group ? "[GROUP]" :
1619 node->is_copy ? "[COPY]" : ""));
1621 if (!node->node_group && !node->parent_link)
1625 // use for dumping artwork info tree
1626 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1627 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1630 if (node->node_group != NULL)
1631 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1637 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1639 return num_leaf_nodes;
1642 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1643 int (*compare_function)(const void *,
1646 int num_nodes = numTreeInfo(*node_first);
1647 TreeInfo **sort_array;
1648 TreeInfo *node = *node_first;
1654 // allocate array for sorting structure pointers
1655 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1657 // writing structure pointers to sorting array
1658 while (i < num_nodes && node) // double boundary check...
1660 sort_array[i] = node;
1666 // sorting the structure pointers in the sorting array
1667 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1670 // update the linkage of list elements with the sorted node array
1671 for (i = 0; i < num_nodes - 1; i++)
1672 sort_array[i]->next = sort_array[i + 1];
1673 sort_array[num_nodes - 1]->next = NULL;
1675 // update the linkage of the main list anchor pointer
1676 *node_first = sort_array[0];
1680 // now recursively sort the level group structures
1684 if (node->node_group != NULL)
1685 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1691 void sortTreeInfo(TreeInfo **node_first)
1693 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1697 // ============================================================================
1698 // some stuff from "files.c"
1699 // ============================================================================
1701 #if defined(PLATFORM_WIN32)
1703 #define S_IRGRP S_IRUSR
1706 #define S_IROTH S_IRUSR
1709 #define S_IWGRP S_IWUSR
1712 #define S_IWOTH S_IWUSR
1715 #define S_IXGRP S_IXUSR
1718 #define S_IXOTH S_IXUSR
1721 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1726 #endif // PLATFORM_WIN32
1728 // file permissions for newly written files
1729 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1730 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1731 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1733 #define MODE_W_PRIVATE (S_IWUSR)
1734 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1735 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1737 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1738 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1739 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1741 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1742 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1743 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1746 char *getHomeDir(void)
1748 static char *dir = NULL;
1750 #if defined(PLATFORM_WIN32)
1753 dir = checked_malloc(MAX_PATH + 1);
1755 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1758 #elif defined(PLATFORM_EMSCRIPTEN)
1759 dir = PERSISTENT_DIRECTORY;
1760 #elif defined(PLATFORM_UNIX)
1763 if ((dir = getenv("HOME")) == NULL)
1765 dir = getUnixHomeDir();
1768 dir = getStringCopy(dir);
1780 char *getPersonalDataDir(void)
1782 static char *personal_data_dir = NULL;
1784 #if defined(PLATFORM_MACOSX)
1785 if (personal_data_dir == NULL)
1786 personal_data_dir = getPath2(getHomeDir(), "Documents");
1788 if (personal_data_dir == NULL)
1789 personal_data_dir = getHomeDir();
1792 return personal_data_dir;
1795 char *getMainUserGameDataDir(void)
1797 static char *main_user_data_dir = NULL;
1799 #if defined(PLATFORM_ANDROID)
1800 if (main_user_data_dir == NULL)
1801 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1802 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1803 SDL_AndroidGetExternalStoragePath() :
1804 SDL_AndroidGetInternalStoragePath());
1806 if (main_user_data_dir == NULL)
1807 main_user_data_dir = getPath2(getPersonalDataDir(),
1808 program.userdata_subdir);
1811 return main_user_data_dir;
1814 char *getUserGameDataDir(void)
1817 return getMainUserGameDataDir();
1819 return getUserDir(user.nr);
1822 char *getSetupDir(void)
1824 return getUserGameDataDir();
1827 static mode_t posix_umask(mode_t mask)
1829 #if defined(PLATFORM_UNIX)
1836 static int posix_mkdir(const char *pathname, mode_t mode)
1838 #if defined(PLATFORM_WIN32)
1839 return mkdir(pathname);
1841 return mkdir(pathname, mode);
1845 static boolean posix_process_running_setgid(void)
1847 #if defined(PLATFORM_UNIX)
1848 return (getgid() != getegid());
1854 void createDirectory(char *dir, char *text, int permission_class)
1856 if (directoryExists(dir))
1859 // leave "other" permissions in umask untouched, but ensure group parts
1860 // of USERDATA_DIR_MODE are not masked
1861 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1862 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1863 mode_t last_umask = posix_umask(0);
1864 mode_t group_umask = ~(dir_mode & S_IRWXG);
1865 int running_setgid = posix_process_running_setgid();
1867 if (permission_class == PERMS_PUBLIC)
1869 // if we're setgid, protect files against "other"
1870 // else keep umask(0) to make the dir world-writable
1873 posix_umask(last_umask & group_umask);
1875 dir_mode = DIR_PERMS_PUBLIC_ALL;
1878 if (posix_mkdir(dir, dir_mode) != 0)
1879 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1881 if (permission_class == PERMS_PUBLIC && !running_setgid)
1882 chmod(dir, dir_mode);
1884 posix_umask(last_umask); // restore previous umask
1887 void InitMainUserDataDirectory(void)
1889 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1892 void InitUserDataDirectory(void)
1894 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1898 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1899 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1903 void SetFilePermissions(char *filename, int permission_class)
1905 int running_setgid = posix_process_running_setgid();
1906 int perms = (permission_class == PERMS_PRIVATE ?
1907 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1909 if (permission_class == PERMS_PUBLIC && !running_setgid)
1910 perms = FILE_PERMS_PUBLIC_ALL;
1912 chmod(filename, perms);
1915 char *getCookie(char *file_type)
1917 static char cookie[MAX_COOKIE_LEN + 1];
1919 if (strlen(program.cookie_prefix) + 1 +
1920 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1921 return "[COOKIE ERROR]"; // should never happen
1923 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1924 program.cookie_prefix, file_type,
1925 program.version_super, program.version_major);
1930 void fprintFileHeader(FILE *file, char *basename)
1932 char *prefix = "# ";
1935 fprintf_line_with_prefix(file, prefix, sep1, 77);
1936 fprintf(file, "%s%s\n", prefix, basename);
1937 fprintf_line_with_prefix(file, prefix, sep1, 77);
1938 fprintf(file, "\n");
1941 int getFileVersionFromCookieString(const char *cookie)
1943 const char *ptr_cookie1, *ptr_cookie2;
1944 const char *pattern1 = "_FILE_VERSION_";
1945 const char *pattern2 = "?.?";
1946 const int len_cookie = strlen(cookie);
1947 const int len_pattern1 = strlen(pattern1);
1948 const int len_pattern2 = strlen(pattern2);
1949 const int len_pattern = len_pattern1 + len_pattern2;
1950 int version_super, version_major;
1952 if (len_cookie <= len_pattern)
1955 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1956 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1958 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1961 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1962 ptr_cookie2[1] != '.' ||
1963 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1966 version_super = ptr_cookie2[0] - '0';
1967 version_major = ptr_cookie2[2] - '0';
1969 return VERSION_IDENT(version_super, version_major, 0, 0);
1972 boolean checkCookieString(const char *cookie, const char *template)
1974 const char *pattern = "_FILE_VERSION_?.?";
1975 const int len_cookie = strlen(cookie);
1976 const int len_template = strlen(template);
1977 const int len_pattern = strlen(pattern);
1979 if (len_cookie != len_template)
1982 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1989 // ----------------------------------------------------------------------------
1990 // setup file list and hash handling functions
1991 // ----------------------------------------------------------------------------
1993 char *getFormattedSetupEntry(char *token, char *value)
1996 static char entry[MAX_LINE_LEN];
1998 // if value is an empty string, just return token without value
2002 // start with the token and some spaces to format output line
2003 sprintf(entry, "%s:", token);
2004 for (i = strlen(entry); i < token_value_position; i++)
2007 // continue with the token's value
2008 strcat(entry, value);
2013 SetupFileList *newSetupFileList(char *token, char *value)
2015 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2017 new->token = getStringCopy(token);
2018 new->value = getStringCopy(value);
2025 void freeSetupFileList(SetupFileList *list)
2030 checked_free(list->token);
2031 checked_free(list->value);
2034 freeSetupFileList(list->next);
2039 char *getListEntry(SetupFileList *list, char *token)
2044 if (strEqual(list->token, token))
2047 return getListEntry(list->next, token);
2050 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2055 if (strEqual(list->token, token))
2057 checked_free(list->value);
2059 list->value = getStringCopy(value);
2063 else if (list->next == NULL)
2064 return (list->next = newSetupFileList(token, value));
2066 return setListEntry(list->next, token, value);
2069 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2074 if (list->next == NULL)
2075 return (list->next = newSetupFileList(token, value));
2077 return addListEntry(list->next, token, value);
2080 #if ENABLE_UNUSED_CODE
2082 static void printSetupFileList(SetupFileList *list)
2087 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2088 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2090 printSetupFileList(list->next);
2096 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2097 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2098 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2099 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2101 #define insert_hash_entry hashtable_insert
2102 #define search_hash_entry hashtable_search
2103 #define change_hash_entry hashtable_change
2104 #define remove_hash_entry hashtable_remove
2107 unsigned int get_hash_from_key(void *key)
2112 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2113 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2114 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2115 it works better than many other constants, prime or not) has never been
2116 adequately explained.
2118 If you just want to have a good hash function, and cannot wait, djb2
2119 is one of the best string hash functions i know. It has excellent
2120 distribution and speed on many different sets of keys and table sizes.
2121 You are not likely to do better with one of the "well known" functions
2122 such as PJW, K&R, etc.
2124 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2127 char *str = (char *)key;
2128 unsigned int hash = 5381;
2131 while ((c = *str++))
2132 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2137 int hash_keys_are_equal(void *key1, void *key2)
2139 return (strEqual((char *)key1, (char *)key2));
2142 SetupFileHash *newSetupFileHash(void)
2144 SetupFileHash *new_hash =
2145 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2147 if (new_hash == NULL)
2148 Fail("create_hashtable() failed -- out of memory");
2153 void freeSetupFileHash(SetupFileHash *hash)
2158 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2161 char *getHashEntry(SetupFileHash *hash, char *token)
2166 return search_hash_entry(hash, token);
2169 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2176 value_copy = getStringCopy(value);
2178 // change value; if it does not exist, insert it as new
2179 if (!change_hash_entry(hash, token, value_copy))
2180 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2181 Fail("cannot insert into hash -- aborting");
2184 char *removeHashEntry(SetupFileHash *hash, char *token)
2189 return remove_hash_entry(hash, token);
2192 #if ENABLE_UNUSED_CODE
2194 static void printSetupFileHash(SetupFileHash *hash)
2196 BEGIN_HASH_ITERATION(hash, itr)
2198 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2199 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2201 END_HASH_ITERATION(hash, itr)
2206 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2207 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2208 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2210 static boolean token_value_separator_found = FALSE;
2211 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2212 static boolean token_value_separator_warning = FALSE;
2214 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2215 static boolean token_already_exists_warning = FALSE;
2218 static boolean getTokenValueFromSetupLineExt(char *line,
2219 char **token_ptr, char **value_ptr,
2220 char *filename, char *line_raw,
2222 boolean separator_required)
2224 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2225 char *token, *value, *line_ptr;
2227 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2228 if (line_raw == NULL)
2230 strncpy(line_copy, line, MAX_LINE_LEN);
2231 line_copy[MAX_LINE_LEN] = '\0';
2234 strcpy(line_raw_copy, line_copy);
2235 line_raw = line_raw_copy;
2238 // cut trailing comment from input line
2239 for (line_ptr = line; *line_ptr; line_ptr++)
2241 if (*line_ptr == '#')
2248 // cut trailing whitespaces from input line
2249 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2250 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2253 // ignore empty lines
2257 // cut leading whitespaces from token
2258 for (token = line; *token; token++)
2259 if (*token != ' ' && *token != '\t')
2262 // start with empty value as reliable default
2265 token_value_separator_found = FALSE;
2267 // find end of token to determine start of value
2268 for (line_ptr = token; *line_ptr; line_ptr++)
2270 // first look for an explicit token/value separator, like ':' or '='
2271 if (*line_ptr == ':' || *line_ptr == '=')
2273 *line_ptr = '\0'; // terminate token string
2274 value = line_ptr + 1; // set beginning of value
2276 token_value_separator_found = TRUE;
2282 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2283 // fallback: if no token/value separator found, also allow whitespaces
2284 if (!token_value_separator_found && !separator_required)
2286 for (line_ptr = token; *line_ptr; line_ptr++)
2288 if (*line_ptr == ' ' || *line_ptr == '\t')
2290 *line_ptr = '\0'; // terminate token string
2291 value = line_ptr + 1; // set beginning of value
2293 token_value_separator_found = TRUE;
2299 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2300 if (token_value_separator_found)
2302 if (!token_value_separator_warning)
2304 Debug("setup", "---");
2306 if (filename != NULL)
2308 Debug("setup", "missing token/value separator(s) in config file:");
2309 Debug("setup", "- config file: '%s'", filename);
2313 Debug("setup", "missing token/value separator(s):");
2316 token_value_separator_warning = TRUE;
2319 if (filename != NULL)
2320 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2322 Debug("setup", "- line: '%s'", line_raw);
2328 // cut trailing whitespaces from token
2329 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2330 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2333 // cut leading whitespaces from value
2334 for (; *value; value++)
2335 if (*value != ' ' && *value != '\t')
2344 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2346 // while the internal (old) interface does not require a token/value
2347 // separator (for downwards compatibility with existing files which
2348 // don't use them), it is mandatory for the external (new) interface
2350 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2353 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2354 boolean top_recursion_level, boolean is_hash)
2356 static SetupFileHash *include_filename_hash = NULL;
2357 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2358 char *token, *value, *line_ptr;
2359 void *insert_ptr = NULL;
2360 boolean read_continued_line = FALSE;
2362 int line_nr = 0, token_count = 0, include_count = 0;
2364 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2365 token_value_separator_warning = FALSE;
2368 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2369 token_already_exists_warning = FALSE;
2372 if (!(file = openFile(filename, MODE_READ)))
2374 #if DEBUG_NO_CONFIG_FILE
2375 Debug("setup", "cannot open configuration file '%s'", filename);
2381 // use "insert pointer" to store list end for constant insertion complexity
2383 insert_ptr = setup_file_data;
2385 // on top invocation, create hash to mark included files (to prevent loops)
2386 if (top_recursion_level)
2387 include_filename_hash = newSetupFileHash();
2389 // mark this file as already included (to prevent including it again)
2390 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2392 while (!checkEndOfFile(file))
2394 // read next line of input file
2395 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2398 // check if line was completely read and is terminated by line break
2399 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2402 // cut trailing line break (this can be newline and/or carriage return)
2403 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2404 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2407 // copy raw input line for later use (mainly debugging output)
2408 strcpy(line_raw, line);
2410 if (read_continued_line)
2412 // append new line to existing line, if there is enough space
2413 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2414 strcat(previous_line, line_ptr);
2416 strcpy(line, previous_line); // copy storage buffer to line
2418 read_continued_line = FALSE;
2421 // if the last character is '\', continue at next line
2422 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2424 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2425 strcpy(previous_line, line); // copy line to storage buffer
2427 read_continued_line = TRUE;
2432 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2433 line_raw, line_nr, FALSE))
2438 if (strEqual(token, "include"))
2440 if (getHashEntry(include_filename_hash, value) == NULL)
2442 char *basepath = getBasePath(filename);
2443 char *basename = getBaseName(value);
2444 char *filename_include = getPath2(basepath, basename);
2446 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2450 free(filename_include);
2456 Warn("ignoring already processed file '%s'", value);
2463 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2465 getHashEntry((SetupFileHash *)setup_file_data, token);
2467 if (old_value != NULL)
2469 if (!token_already_exists_warning)
2471 Debug("setup", "---");
2472 Debug("setup", "duplicate token(s) found in config file:");
2473 Debug("setup", "- config file: '%s'", filename);
2475 token_already_exists_warning = TRUE;
2478 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2479 Debug("setup", " old value: '%s'", old_value);
2480 Debug("setup", " new value: '%s'", value);
2484 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2488 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2498 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2499 if (token_value_separator_warning)
2500 Debug("setup", "---");
2503 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2504 if (token_already_exists_warning)
2505 Debug("setup", "---");
2508 if (token_count == 0 && include_count == 0)
2509 Warn("configuration file '%s' is empty", filename);
2511 if (top_recursion_level)
2512 freeSetupFileHash(include_filename_hash);
2517 static int compareSetupFileData(const void *object1, const void *object2)
2519 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2520 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2522 return strcmp(entry1->token, entry2->token);
2525 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2527 int item_count = hashtable_count(hash);
2528 int item_size = sizeof(struct ConfigInfo);
2529 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2533 // copy string pointers from hash to array
2534 BEGIN_HASH_ITERATION(hash, itr)
2536 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2537 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2541 if (i > item_count) // should never happen
2544 END_HASH_ITERATION(hash, itr)
2546 // sort string pointers from hash in array
2547 qsort(sort_array, item_count, item_size, compareSetupFileData);
2549 if (!(file = fopen(filename, MODE_WRITE)))
2551 Warn("cannot write configuration file '%s'", filename);
2556 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2557 program.version_string));
2558 for (i = 0; i < item_count; i++)
2559 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2560 sort_array[i].value));
2563 checked_free(sort_array);
2566 SetupFileList *loadSetupFileList(char *filename)
2568 SetupFileList *setup_file_list = newSetupFileList("", "");
2569 SetupFileList *first_valid_list_entry;
2571 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2573 freeSetupFileList(setup_file_list);
2578 first_valid_list_entry = setup_file_list->next;
2580 // free empty list header
2581 setup_file_list->next = NULL;
2582 freeSetupFileList(setup_file_list);
2584 return first_valid_list_entry;
2587 SetupFileHash *loadSetupFileHash(char *filename)
2589 SetupFileHash *setup_file_hash = newSetupFileHash();
2591 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2593 freeSetupFileHash(setup_file_hash);
2598 return setup_file_hash;
2602 // ============================================================================
2604 // ============================================================================
2606 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2607 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2608 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2609 #define TOKEN_STR_LAST_USER "last_user"
2611 // level directory info
2612 #define LEVELINFO_TOKEN_IDENTIFIER 0
2613 #define LEVELINFO_TOKEN_NAME 1
2614 #define LEVELINFO_TOKEN_NAME_SORTING 2
2615 #define LEVELINFO_TOKEN_AUTHOR 3
2616 #define LEVELINFO_TOKEN_YEAR 4
2617 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2618 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2619 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2620 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2621 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2622 #define LEVELINFO_TOKEN_TESTED_BY 10
2623 #define LEVELINFO_TOKEN_LEVELS 11
2624 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2625 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2626 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2627 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2628 #define LEVELINFO_TOKEN_READONLY 16
2629 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2630 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2631 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2632 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2633 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2634 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2635 #define LEVELINFO_TOKEN_MUSIC_SET 23
2636 #define LEVELINFO_TOKEN_FILENAME 24
2637 #define LEVELINFO_TOKEN_FILETYPE 25
2638 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2639 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2640 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2641 #define LEVELINFO_TOKEN_HANDICAP 29
2642 #define LEVELINFO_TOKEN_SKIP_LEVELS 30
2643 #define LEVELINFO_TOKEN_USE_EMC_TILES 31
2645 #define NUM_LEVELINFO_TOKENS 32
2647 static LevelDirTree ldi;
2649 static struct TokenInfo levelinfo_tokens[] =
2651 // level directory info
2652 { TYPE_STRING, &ldi.identifier, "identifier" },
2653 { TYPE_STRING, &ldi.name, "name" },
2654 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2655 { TYPE_STRING, &ldi.author, "author" },
2656 { TYPE_STRING, &ldi.year, "year" },
2657 { TYPE_STRING, &ldi.program_title, "program_title" },
2658 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2659 { TYPE_STRING, &ldi.program_company, "program_company" },
2660 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2661 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2662 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2663 { TYPE_INTEGER, &ldi.levels, "levels" },
2664 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2665 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2666 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2667 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2668 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2669 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2670 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2671 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2672 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2673 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2674 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2675 { TYPE_STRING, &ldi.music_set, "music_set" },
2676 { TYPE_STRING, &ldi.level_filename, "filename" },
2677 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2678 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2679 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2680 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2681 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2682 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2683 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2686 static struct TokenInfo artworkinfo_tokens[] =
2688 // artwork directory info
2689 { TYPE_STRING, &ldi.identifier, "identifier" },
2690 { TYPE_STRING, &ldi.subdir, "subdir" },
2691 { TYPE_STRING, &ldi.name, "name" },
2692 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2693 { TYPE_STRING, &ldi.author, "author" },
2694 { TYPE_STRING, &ldi.program_title, "program_title" },
2695 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2696 { TYPE_STRING, &ldi.program_company, "program_company" },
2697 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2698 { TYPE_STRING, &ldi.basepath, "basepath" },
2699 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2700 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2701 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2706 static char *optional_tokens[] =
2709 "program_copyright",
2715 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2719 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2720 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2721 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2722 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2725 ti->node_parent = NULL;
2726 ti->node_group = NULL;
2733 ti->fullpath = NULL;
2734 ti->basepath = NULL;
2735 ti->identifier = NULL;
2736 ti->name = getStringCopy(ANONYMOUS_NAME);
2737 ti->name_sorting = NULL;
2738 ti->author = getStringCopy(ANONYMOUS_NAME);
2741 ti->program_title = NULL;
2742 ti->program_copyright = NULL;
2743 ti->program_company = NULL;
2745 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2746 ti->latest_engine = FALSE; // default: get from level
2747 ti->parent_link = FALSE;
2748 ti->is_copy = FALSE;
2749 ti->in_user_dir = FALSE;
2750 ti->user_defined = FALSE;
2752 ti->class_desc = NULL;
2754 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2756 if (ti->type == TREE_TYPE_LEVEL_DIR)
2758 ti->imported_from = NULL;
2759 ti->imported_by = NULL;
2760 ti->tested_by = NULL;
2762 ti->graphics_set_ecs = NULL;
2763 ti->graphics_set_aga = NULL;
2764 ti->graphics_set = NULL;
2765 ti->sounds_set_default = NULL;
2766 ti->sounds_set_lowpass = NULL;
2767 ti->sounds_set = NULL;
2768 ti->music_set = NULL;
2769 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2770 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2771 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2773 ti->level_filename = NULL;
2774 ti->level_filetype = NULL;
2776 ti->special_flags = NULL;
2778 ti->empty_level_name = NULL;
2779 ti->force_level_name = FALSE;
2782 ti->first_level = 0;
2784 ti->level_group = FALSE;
2785 ti->handicap_level = 0;
2786 ti->readonly = TRUE;
2787 ti->handicap = TRUE;
2788 ti->skip_levels = FALSE;
2790 ti->use_emc_tiles = FALSE;
2794 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2798 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2800 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2805 // copy all values from the parent structure
2807 ti->type = parent->type;
2809 ti->node_top = parent->node_top;
2810 ti->node_parent = parent;
2811 ti->node_group = NULL;
2818 ti->fullpath = NULL;
2819 ti->basepath = NULL;
2820 ti->identifier = NULL;
2821 ti->name = getStringCopy(ANONYMOUS_NAME);
2822 ti->name_sorting = NULL;
2823 ti->author = getStringCopy(parent->author);
2824 ti->year = getStringCopy(parent->year);
2826 ti->program_title = getStringCopy(parent->program_title);
2827 ti->program_copyright = getStringCopy(parent->program_copyright);
2828 ti->program_company = getStringCopy(parent->program_company);
2830 ti->sort_priority = parent->sort_priority;
2831 ti->latest_engine = parent->latest_engine;
2832 ti->parent_link = FALSE;
2833 ti->is_copy = FALSE;
2834 ti->in_user_dir = parent->in_user_dir;
2835 ti->user_defined = parent->user_defined;
2836 ti->color = parent->color;
2837 ti->class_desc = getStringCopy(parent->class_desc);
2839 ti->infotext = getStringCopy(parent->infotext);
2841 if (ti->type == TREE_TYPE_LEVEL_DIR)
2843 ti->imported_from = getStringCopy(parent->imported_from);
2844 ti->imported_by = getStringCopy(parent->imported_by);
2845 ti->tested_by = getStringCopy(parent->tested_by);
2847 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2848 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2849 ti->graphics_set = getStringCopy(parent->graphics_set);
2850 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2851 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2852 ti->sounds_set = getStringCopy(parent->sounds_set);
2853 ti->music_set = getStringCopy(parent->music_set);
2854 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2855 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2856 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2858 ti->level_filename = getStringCopy(parent->level_filename);
2859 ti->level_filetype = getStringCopy(parent->level_filetype);
2861 ti->special_flags = getStringCopy(parent->special_flags);
2863 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2864 ti->force_level_name = parent->force_level_name;
2866 ti->levels = parent->levels;
2867 ti->first_level = parent->first_level;
2868 ti->last_level = parent->last_level;
2869 ti->level_group = FALSE;
2870 ti->handicap_level = parent->handicap_level;
2871 ti->readonly = parent->readonly;
2872 ti->handicap = parent->handicap;
2873 ti->skip_levels = parent->skip_levels;
2875 ti->use_emc_tiles = parent->use_emc_tiles;
2879 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2881 TreeInfo *ti_copy = newTreeInfo();
2883 // copy all values from the original structure
2885 ti_copy->type = ti->type;
2887 ti_copy->node_top = ti->node_top;
2888 ti_copy->node_parent = ti->node_parent;
2889 ti_copy->node_group = ti->node_group;
2890 ti_copy->next = ti->next;
2892 ti_copy->cl_first = ti->cl_first;
2893 ti_copy->cl_cursor = ti->cl_cursor;
2895 ti_copy->subdir = getStringCopy(ti->subdir);
2896 ti_copy->fullpath = getStringCopy(ti->fullpath);
2897 ti_copy->basepath = getStringCopy(ti->basepath);
2898 ti_copy->identifier = getStringCopy(ti->identifier);
2899 ti_copy->name = getStringCopy(ti->name);
2900 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2901 ti_copy->author = getStringCopy(ti->author);
2902 ti_copy->year = getStringCopy(ti->year);
2904 ti_copy->program_title = getStringCopy(ti->program_title);
2905 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2906 ti_copy->program_company = getStringCopy(ti->program_company);
2908 ti_copy->imported_from = getStringCopy(ti->imported_from);
2909 ti_copy->imported_by = getStringCopy(ti->imported_by);
2910 ti_copy->tested_by = getStringCopy(ti->tested_by);
2912 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2913 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2914 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2915 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2916 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2917 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2918 ti_copy->music_set = getStringCopy(ti->music_set);
2919 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2920 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2921 ti_copy->music_path = getStringCopy(ti->music_path);
2923 ti_copy->level_filename = getStringCopy(ti->level_filename);
2924 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2926 ti_copy->special_flags = getStringCopy(ti->special_flags);
2928 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
2929 ti_copy->force_level_name = ti->force_level_name;
2931 ti_copy->levels = ti->levels;
2932 ti_copy->first_level = ti->first_level;
2933 ti_copy->last_level = ti->last_level;
2934 ti_copy->sort_priority = ti->sort_priority;
2936 ti_copy->latest_engine = ti->latest_engine;
2938 ti_copy->level_group = ti->level_group;
2939 ti_copy->parent_link = ti->parent_link;
2940 ti_copy->is_copy = ti->is_copy;
2941 ti_copy->in_user_dir = ti->in_user_dir;
2942 ti_copy->user_defined = ti->user_defined;
2943 ti_copy->readonly = ti->readonly;
2944 ti_copy->handicap = ti->handicap;
2945 ti_copy->skip_levels = ti->skip_levels;
2947 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2949 ti_copy->color = ti->color;
2950 ti_copy->class_desc = getStringCopy(ti->class_desc);
2951 ti_copy->handicap_level = ti->handicap_level;
2953 ti_copy->infotext = getStringCopy(ti->infotext);
2958 void freeTreeInfo(TreeInfo *ti)
2963 checked_free(ti->subdir);
2964 checked_free(ti->fullpath);
2965 checked_free(ti->basepath);
2966 checked_free(ti->identifier);
2968 checked_free(ti->name);
2969 checked_free(ti->name_sorting);
2970 checked_free(ti->author);
2971 checked_free(ti->year);
2973 checked_free(ti->program_title);
2974 checked_free(ti->program_copyright);
2975 checked_free(ti->program_company);
2977 checked_free(ti->class_desc);
2979 checked_free(ti->infotext);
2981 if (ti->type == TREE_TYPE_LEVEL_DIR)
2983 checked_free(ti->imported_from);
2984 checked_free(ti->imported_by);
2985 checked_free(ti->tested_by);
2987 checked_free(ti->graphics_set_ecs);
2988 checked_free(ti->graphics_set_aga);
2989 checked_free(ti->graphics_set);
2990 checked_free(ti->sounds_set_default);
2991 checked_free(ti->sounds_set_lowpass);
2992 checked_free(ti->sounds_set);
2993 checked_free(ti->music_set);
2995 checked_free(ti->graphics_path);
2996 checked_free(ti->sounds_path);
2997 checked_free(ti->music_path);
2999 checked_free(ti->level_filename);
3000 checked_free(ti->level_filetype);
3002 checked_free(ti->special_flags);
3005 // recursively free child node
3007 freeTreeInfo(ti->node_group);
3009 // recursively free next node
3011 freeTreeInfo(ti->next);
3016 void setSetupInfo(struct TokenInfo *token_info,
3017 int token_nr, char *token_value)
3019 int token_type = token_info[token_nr].type;
3020 void *setup_value = token_info[token_nr].value;
3022 if (token_value == NULL)
3025 // set setup field to corresponding token value
3030 *(boolean *)setup_value = get_boolean_from_string(token_value);
3034 *(int *)setup_value = get_switch3_from_string(token_value);
3038 *(Key *)setup_value = getKeyFromKeyName(token_value);
3042 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3046 *(int *)setup_value = get_integer_from_string(token_value);
3050 checked_free(*(char **)setup_value);
3051 *(char **)setup_value = getStringCopy(token_value);
3055 *(int *)setup_value = get_player_nr_from_string(token_value);
3063 static int compareTreeInfoEntries(const void *object1, const void *object2)
3065 const TreeInfo *entry1 = *((TreeInfo **)object1);
3066 const TreeInfo *entry2 = *((TreeInfo **)object2);
3067 int tree_sorting1 = TREE_SORTING(entry1);
3068 int tree_sorting2 = TREE_SORTING(entry2);
3070 if (tree_sorting1 != tree_sorting2)
3071 return (tree_sorting1 - tree_sorting2);
3073 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3076 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3080 if (node_parent == NULL)
3083 ti_new = newTreeInfo();
3084 setTreeInfoToDefaults(ti_new, node_parent->type);
3086 ti_new->node_parent = node_parent;
3087 ti_new->parent_link = TRUE;
3089 setString(&ti_new->identifier, node_parent->identifier);
3090 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3091 setString(&ti_new->name_sorting, ti_new->name);
3093 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3094 setString(&ti_new->fullpath, node_parent->fullpath);
3096 ti_new->sort_priority = LEVELCLASS_PARENT;
3097 ti_new->latest_engine = node_parent->latest_engine;
3099 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3101 pushTreeInfo(&node_parent->node_group, ti_new);
3106 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3108 if (node_first == NULL)
3111 TreeInfo *ti_new = newTreeInfo();
3112 int type = node_first->type;
3114 setTreeInfoToDefaults(ti_new, type);
3116 ti_new->node_parent = NULL;
3117 ti_new->parent_link = FALSE;
3119 setString(&ti_new->identifier, "top_tree_node");
3120 setString(&ti_new->name, TREE_INFOTEXT(type));
3121 setString(&ti_new->name_sorting, ti_new->name);
3123 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3124 setString(&ti_new->fullpath, ".");
3126 ti_new->sort_priority = LEVELCLASS_TOP;
3127 ti_new->latest_engine = node_first->latest_engine;
3129 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3131 ti_new->node_group = node_first;
3132 ti_new->level_group = TRUE;
3134 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3136 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3137 setString(&ti_new2->name_sorting, ti_new2->name);
3142 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3146 if (node->node_group)
3147 setTreeInfoParentNodes(node->node_group, node);
3149 node->node_parent = node_parent;
3155 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3157 // add top tree node with back link node in previous tree
3158 node_first = createTopTreeInfoNode(node_first);
3160 // set all parent links (back links) in complete tree
3161 setTreeInfoParentNodes(node_first, NULL);
3167 // ----------------------------------------------------------------------------
3168 // functions for handling level and custom artwork info cache
3169 // ----------------------------------------------------------------------------
3171 static void LoadArtworkInfoCache(void)
3173 InitCacheDirectory();
3175 if (artworkinfo_cache_old == NULL)
3177 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3179 // try to load artwork info hash from already existing cache file
3180 artworkinfo_cache_old = loadSetupFileHash(filename);
3182 // try to get program version that artwork info cache was written with
3183 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3185 // check program version of artwork info cache against current version
3186 if (!strEqual(version, program.version_string))
3188 freeSetupFileHash(artworkinfo_cache_old);
3190 artworkinfo_cache_old = NULL;
3193 // if no artwork info cache file was found, start with empty hash
3194 if (artworkinfo_cache_old == NULL)
3195 artworkinfo_cache_old = newSetupFileHash();
3200 if (artworkinfo_cache_new == NULL)
3201 artworkinfo_cache_new = newSetupFileHash();
3203 update_artworkinfo_cache = FALSE;
3206 static void SaveArtworkInfoCache(void)
3208 if (!update_artworkinfo_cache)
3211 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3213 InitCacheDirectory();
3215 saveSetupFileHash(artworkinfo_cache_new, filename);
3220 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3222 static char *prefix = NULL;
3224 checked_free(prefix);
3226 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3231 // (identical to above function, but separate string buffer needed -- nasty)
3232 static char *getCacheToken(char *prefix, char *suffix)
3234 static char *token = NULL;
3236 checked_free(token);
3238 token = getStringCat2WithSeparator(prefix, suffix, ".");
3243 static char *getFileTimestampString(char *filename)
3245 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3248 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3250 struct stat file_status;
3252 if (timestamp_string == NULL)
3255 if (!fileExists(filename)) // file does not exist
3256 return (atoi(timestamp_string) != 0);
3258 if (stat(filename, &file_status) != 0) // cannot stat file
3261 return (file_status.st_mtime != atoi(timestamp_string));
3264 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3266 char *identifier = level_node->subdir;
3267 char *type_string = ARTWORK_DIRECTORY(type);
3268 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3269 char *token_main = getCacheToken(token_prefix, "CACHED");
3270 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3271 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3272 TreeInfo *artwork_info = NULL;
3274 if (!use_artworkinfo_cache)
3277 if (optional_tokens_hash == NULL)
3281 // create hash from list of optional tokens (for quick access)
3282 optional_tokens_hash = newSetupFileHash();
3283 for (i = 0; optional_tokens[i] != NULL; i++)
3284 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3291 artwork_info = newTreeInfo();
3292 setTreeInfoToDefaults(artwork_info, type);
3294 // set all structure fields according to the token/value pairs
3295 ldi = *artwork_info;
3296 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3298 char *token_suffix = artworkinfo_tokens[i].text;
3299 char *token = getCacheToken(token_prefix, token_suffix);
3300 char *value = getHashEntry(artworkinfo_cache_old, token);
3302 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3304 setSetupInfo(artworkinfo_tokens, i, value);
3306 // check if cache entry for this item is mandatory, but missing
3307 if (value == NULL && !optional)
3309 Warn("missing cache entry '%s'", token);
3315 *artwork_info = ldi;
3320 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3321 LEVELINFO_FILENAME);
3322 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3323 ARTWORKINFO_FILENAME(type));
3325 // check if corresponding "levelinfo.conf" file has changed
3326 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3327 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3329 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3332 // check if corresponding "<artworkinfo>.conf" file has changed
3333 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3334 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3336 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3339 checked_free(filename_levelinfo);
3340 checked_free(filename_artworkinfo);
3343 if (!cached && artwork_info != NULL)
3345 freeTreeInfo(artwork_info);
3350 return artwork_info;
3353 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3354 LevelDirTree *level_node, int type)
3356 char *identifier = level_node->subdir;
3357 char *type_string = ARTWORK_DIRECTORY(type);
3358 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3359 char *token_main = getCacheToken(token_prefix, "CACHED");
3360 boolean set_cache_timestamps = TRUE;
3363 setHashEntry(artworkinfo_cache_new, token_main, "true");
3365 if (set_cache_timestamps)
3367 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3368 LEVELINFO_FILENAME);
3369 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3370 ARTWORKINFO_FILENAME(type));
3371 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3372 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3374 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3375 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3377 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3378 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3380 checked_free(filename_levelinfo);
3381 checked_free(filename_artworkinfo);
3382 checked_free(timestamp_levelinfo);
3383 checked_free(timestamp_artworkinfo);
3386 ldi = *artwork_info;
3387 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3389 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3390 char *value = getSetupValue(artworkinfo_tokens[i].type,
3391 artworkinfo_tokens[i].value);
3393 setHashEntry(artworkinfo_cache_new, token, value);
3398 // ----------------------------------------------------------------------------
3399 // functions for loading level info and custom artwork info
3400 // ----------------------------------------------------------------------------
3402 int GetZipFileTreeType(char *zip_filename)
3404 static char *top_dir_path = NULL;
3405 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3406 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3408 GRAPHICSINFO_FILENAME,
3409 SOUNDSINFO_FILENAME,
3415 checked_free(top_dir_path);
3416 top_dir_path = NULL;
3418 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3420 checked_free(top_dir_conf_filename[j]);
3421 top_dir_conf_filename[j] = NULL;
3424 char **zip_entries = zip_list(zip_filename);
3426 // check if zip file successfully opened
3427 if (zip_entries == NULL || zip_entries[0] == NULL)
3428 return TREE_TYPE_UNDEFINED;
3430 // first zip file entry is expected to be top level directory
3431 char *top_dir = zip_entries[0];
3433 // check if valid top level directory found in zip file
3434 if (!strSuffix(top_dir, "/"))
3435 return TREE_TYPE_UNDEFINED;
3437 // get filenames of valid configuration files in top level directory
3438 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3439 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3441 int tree_type = TREE_TYPE_UNDEFINED;
3444 while (zip_entries[e] != NULL)
3446 // check if every zip file entry is below top level directory
3447 if (!strPrefix(zip_entries[e], top_dir))
3448 return TREE_TYPE_UNDEFINED;
3450 // check if this zip file entry is a valid configuration filename
3451 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3453 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3455 // only exactly one valid configuration file allowed
3456 if (tree_type != TREE_TYPE_UNDEFINED)
3457 return TREE_TYPE_UNDEFINED;
3469 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3472 static char *top_dir_path = NULL;
3473 static char *top_dir_conf_filename = NULL;
3475 checked_free(top_dir_path);
3476 checked_free(top_dir_conf_filename);
3478 top_dir_path = NULL;
3479 top_dir_conf_filename = NULL;
3481 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3482 ARTWORKINFO_FILENAME(tree_type));
3484 // check if valid configuration filename determined
3485 if (conf_basename == NULL || strEqual(conf_basename, ""))
3488 char **zip_entries = zip_list(zip_filename);
3490 // check if zip file successfully opened
3491 if (zip_entries == NULL || zip_entries[0] == NULL)
3494 // first zip file entry is expected to be top level directory
3495 char *top_dir = zip_entries[0];
3497 // check if valid top level directory found in zip file
3498 if (!strSuffix(top_dir, "/"))
3501 // get path of extracted top level directory
3502 top_dir_path = getPath2(directory, top_dir);
3504 // remove trailing directory separator from top level directory path
3505 // (required to be able to check for file and directory in next step)
3506 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3508 // check if zip file's top level directory already exists in target directory
3509 if (fileExists(top_dir_path)) // (checks for file and directory)
3512 // get filename of configuration file in top level directory
3513 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3515 boolean found_top_dir_conf_filename = FALSE;
3518 while (zip_entries[i] != NULL)
3520 // check if every zip file entry is below top level directory
3521 if (!strPrefix(zip_entries[i], top_dir))
3524 // check if this zip file entry is the configuration filename
3525 if (strEqual(zip_entries[i], top_dir_conf_filename))
3526 found_top_dir_conf_filename = TRUE;
3531 // check if valid configuration filename was found in zip file
3532 if (!found_top_dir_conf_filename)
3538 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3541 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3544 if (!zip_file_valid)
3546 Warn("zip file '%s' rejected!", zip_filename);
3551 char **zip_entries = zip_extract(zip_filename, directory);
3553 if (zip_entries == NULL)
3555 Warn("zip file '%s' could not be extracted!", zip_filename);
3560 Info("zip file '%s' successfully extracted!", zip_filename);
3562 // first zip file entry contains top level directory
3563 char *top_dir = zip_entries[0];
3565 // remove trailing directory separator from top level directory
3566 top_dir[strlen(top_dir) - 1] = '\0';
3571 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3574 DirectoryEntry *dir_entry;
3576 if ((dir = openDirectory(directory)) == NULL)
3578 // display error if directory is main "options.graphics_directory" etc.
3579 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3580 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3581 Warn("cannot read directory '%s'", directory);
3586 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3588 // skip non-zip files (and also directories with zip extension)
3589 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3592 char *zip_filename = getPath2(directory, dir_entry->basename);
3593 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3594 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3596 // check if zip file hasn't already been extracted or rejected
3597 if (!fileExists(zip_filename_extracted) &&
3598 !fileExists(zip_filename_rejected))
3600 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3602 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3603 zip_filename_rejected);
3606 // create empty file to mark zip file as extracted or rejected
3607 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3608 fclose(marker_file);
3611 free(zip_filename_extracted);
3612 free(zip_filename_rejected);
3616 closeDirectory(dir);
3619 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3620 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3622 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3623 TreeInfo *node_parent,
3624 char *level_directory,
3625 char *directory_name)
3627 char *directory_path = getPath2(level_directory, directory_name);
3628 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3629 SetupFileHash *setup_file_hash;
3630 LevelDirTree *leveldir_new = NULL;
3633 // unless debugging, silently ignore directories without "levelinfo.conf"
3634 if (!options.debug && !fileExists(filename))
3636 free(directory_path);
3642 setup_file_hash = loadSetupFileHash(filename);
3644 if (setup_file_hash == NULL)
3646 #if DEBUG_NO_CONFIG_FILE
3647 Debug("setup", "ignoring level directory '%s'", directory_path);
3650 free(directory_path);
3656 leveldir_new = newTreeInfo();
3659 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3661 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3663 leveldir_new->subdir = getStringCopy(directory_name);
3665 // set all structure fields according to the token/value pairs
3666 ldi = *leveldir_new;
3667 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3668 setSetupInfo(levelinfo_tokens, i,
3669 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3670 *leveldir_new = ldi;
3672 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3673 setString(&leveldir_new->name, leveldir_new->subdir);
3675 if (leveldir_new->identifier == NULL)
3676 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3678 if (leveldir_new->name_sorting == NULL)
3679 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3681 if (node_parent == NULL) // top level group
3683 leveldir_new->basepath = getStringCopy(level_directory);
3684 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3686 else // sub level group
3688 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3689 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3692 leveldir_new->last_level =
3693 leveldir_new->first_level + leveldir_new->levels - 1;
3695 leveldir_new->in_user_dir =
3696 (!strEqual(leveldir_new->basepath, options.level_directory));
3698 // adjust some settings if user's private level directory was detected
3699 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3700 leveldir_new->in_user_dir &&
3701 (strEqual(leveldir_new->subdir, getLoginName()) ||
3702 strEqual(leveldir_new->name, getLoginName()) ||
3703 strEqual(leveldir_new->author, getRealName())))
3705 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3706 leveldir_new->readonly = FALSE;
3709 leveldir_new->user_defined =
3710 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3712 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3714 leveldir_new->handicap_level = // set handicap to default value
3715 (leveldir_new->user_defined || !leveldir_new->handicap ?
3716 leveldir_new->last_level : leveldir_new->first_level);
3718 DrawInitTextItem(leveldir_new->name);
3720 pushTreeInfo(node_first, leveldir_new);
3722 freeSetupFileHash(setup_file_hash);
3724 if (leveldir_new->level_group)
3726 // create node to link back to current level directory
3727 createParentTreeInfoNode(leveldir_new);
3729 // recursively step into sub-directory and look for more level series
3730 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3731 leveldir_new, directory_path);
3734 free(directory_path);
3740 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3741 TreeInfo *node_parent,
3742 char *level_directory)
3744 // ---------- 1st stage: process any level set zip files ----------
3746 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3748 // ---------- 2nd stage: check for level set directories ----------
3751 DirectoryEntry *dir_entry;
3752 boolean valid_entry_found = FALSE;
3754 if ((dir = openDirectory(level_directory)) == NULL)
3756 Warn("cannot read level directory '%s'", level_directory);
3761 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3763 char *directory_name = dir_entry->basename;
3764 char *directory_path = getPath2(level_directory, directory_name);
3766 // skip entries for current and parent directory
3767 if (strEqual(directory_name, ".") ||
3768 strEqual(directory_name, ".."))
3770 free(directory_path);
3775 // find out if directory entry is itself a directory
3776 if (!dir_entry->is_directory) // not a directory
3778 free(directory_path);
3783 free(directory_path);
3785 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3786 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3787 strEqual(directory_name, MUSIC_DIRECTORY))
3790 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3795 closeDirectory(dir);
3797 // special case: top level directory may directly contain "levelinfo.conf"
3798 if (node_parent == NULL && !valid_entry_found)
3800 // check if this directory directly contains a file "levelinfo.conf"
3801 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3802 level_directory, ".");
3805 if (!valid_entry_found)
3806 Warn("cannot find any valid level series in directory '%s'",
3810 boolean AdjustGraphicsForEMC(void)
3812 boolean settings_changed = FALSE;
3814 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3815 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3817 return settings_changed;
3820 boolean AdjustSoundsForEMC(void)
3822 boolean settings_changed = FALSE;
3824 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3825 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3827 return settings_changed;
3830 void LoadLevelInfo(void)
3832 InitUserLevelDirectory(getLoginName());
3834 DrawInitTextHead("Loading level series");
3836 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3837 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3839 leveldir_first = createTopTreeInfoNode(leveldir_first);
3841 /* after loading all level set information, clone the level directory tree
3842 and remove all level sets without levels (these may still contain artwork
3843 to be offered in the setup menu as "custom artwork", and are therefore
3844 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3845 leveldir_first_all = leveldir_first;
3846 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3848 AdjustGraphicsForEMC();
3849 AdjustSoundsForEMC();
3851 // before sorting, the first entries will be from the user directory
3852 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3854 if (leveldir_first == NULL)
3855 Fail("cannot find any valid level series in any directory");
3857 sortTreeInfo(&leveldir_first);
3859 #if ENABLE_UNUSED_CODE
3860 dumpTreeInfo(leveldir_first, 0);
3864 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3865 TreeInfo *node_parent,
3866 char *base_directory,
3867 char *directory_name, int type)
3869 char *directory_path = getPath2(base_directory, directory_name);
3870 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3871 SetupFileHash *setup_file_hash = NULL;
3872 TreeInfo *artwork_new = NULL;
3875 if (fileExists(filename))
3876 setup_file_hash = loadSetupFileHash(filename);
3878 if (setup_file_hash == NULL) // no config file -- look for artwork files
3881 DirectoryEntry *dir_entry;
3882 boolean valid_file_found = FALSE;
3884 if ((dir = openDirectory(directory_path)) != NULL)
3886 while ((dir_entry = readDirectory(dir)) != NULL)
3888 if (FileIsArtworkType(dir_entry->filename, type))
3890 valid_file_found = TRUE;
3896 closeDirectory(dir);
3899 if (!valid_file_found)
3901 #if DEBUG_NO_CONFIG_FILE
3902 if (!strEqual(directory_name, "."))
3903 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3906 free(directory_path);
3913 artwork_new = newTreeInfo();
3916 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3918 setTreeInfoToDefaults(artwork_new, type);
3920 artwork_new->subdir = getStringCopy(directory_name);
3922 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3924 // set all structure fields according to the token/value pairs
3926 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3927 setSetupInfo(levelinfo_tokens, i,
3928 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3931 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3932 setString(&artwork_new->name, artwork_new->subdir);
3934 if (artwork_new->identifier == NULL)
3935 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3937 if (artwork_new->name_sorting == NULL)
3938 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3941 if (node_parent == NULL) // top level group
3943 artwork_new->basepath = getStringCopy(base_directory);
3944 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3946 else // sub level group
3948 artwork_new->basepath = getStringCopy(node_parent->basepath);
3949 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3952 artwork_new->in_user_dir =
3953 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3955 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3957 if (setup_file_hash == NULL) // (after determining ".user_defined")
3959 if (strEqual(artwork_new->subdir, "."))
3961 if (artwork_new->user_defined)
3963 setString(&artwork_new->identifier, "private");
3964 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3968 setString(&artwork_new->identifier, "classic");
3969 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3972 setString(&artwork_new->class_desc,
3973 getLevelClassDescription(artwork_new));
3977 setString(&artwork_new->identifier, artwork_new->subdir);
3980 setString(&artwork_new->name, artwork_new->identifier);
3981 setString(&artwork_new->name_sorting, artwork_new->name);
3984 pushTreeInfo(node_first, artwork_new);
3986 freeSetupFileHash(setup_file_hash);
3988 free(directory_path);
3994 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3995 TreeInfo *node_parent,
3996 char *base_directory, int type)
3998 // ---------- 1st stage: process any artwork set zip files ----------
4000 ProcessZipFilesInDirectory(base_directory, type);
4002 // ---------- 2nd stage: check for artwork set directories ----------
4005 DirectoryEntry *dir_entry;
4006 boolean valid_entry_found = FALSE;
4008 if ((dir = openDirectory(base_directory)) == NULL)
4010 // display error if directory is main "options.graphics_directory" etc.
4011 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4012 Warn("cannot read directory '%s'", base_directory);
4017 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4019 char *directory_name = dir_entry->basename;
4020 char *directory_path = getPath2(base_directory, directory_name);
4022 // skip directory entries for current and parent directory
4023 if (strEqual(directory_name, ".") ||
4024 strEqual(directory_name, ".."))
4026 free(directory_path);
4031 // skip directory entries which are not a directory
4032 if (!dir_entry->is_directory) // not a directory
4034 free(directory_path);
4039 free(directory_path);
4041 // check if this directory contains artwork with or without config file
4042 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4044 directory_name, type);
4047 closeDirectory(dir);
4049 // check if this directory directly contains artwork itself
4050 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4051 base_directory, ".",
4053 if (!valid_entry_found)
4054 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4057 static TreeInfo *getDummyArtworkInfo(int type)
4059 // this is only needed when there is completely no artwork available
4060 TreeInfo *artwork_new = newTreeInfo();
4062 setTreeInfoToDefaults(artwork_new, type);
4064 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4065 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4066 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4068 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4069 setString(&artwork_new->name, UNDEFINED_FILENAME);
4070 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4075 void SetCurrentArtwork(int type)
4077 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4078 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4079 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4080 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4082 // set current artwork to artwork configured in setup menu
4083 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4085 // if not found, set current artwork to default artwork
4086 if (*current_ptr == NULL)
4087 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4089 // if not found, set current artwork to first artwork in tree
4090 if (*current_ptr == NULL)
4091 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4094 void ChangeCurrentArtworkIfNeeded(int type)
4096 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4097 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4099 if (!strEqual(current_identifier, setup_set))
4100 SetCurrentArtwork(type);
4103 void LoadArtworkInfo(void)
4105 LoadArtworkInfoCache();
4107 DrawInitTextHead("Looking for custom artwork");
4109 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4110 options.graphics_directory,
4111 TREE_TYPE_GRAPHICS_DIR);
4112 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4113 getUserGraphicsDir(),
4114 TREE_TYPE_GRAPHICS_DIR);
4116 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4117 options.sounds_directory,
4118 TREE_TYPE_SOUNDS_DIR);
4119 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4121 TREE_TYPE_SOUNDS_DIR);
4123 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4124 options.music_directory,
4125 TREE_TYPE_MUSIC_DIR);
4126 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4128 TREE_TYPE_MUSIC_DIR);
4130 if (artwork.gfx_first == NULL)
4131 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4132 if (artwork.snd_first == NULL)
4133 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4134 if (artwork.mus_first == NULL)
4135 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4137 // before sorting, the first entries will be from the user directory
4138 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4139 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4140 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4142 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4143 artwork.snd_current_identifier = artwork.snd_current->identifier;
4144 artwork.mus_current_identifier = artwork.mus_current->identifier;
4146 #if ENABLE_UNUSED_CODE
4147 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4148 artwork.gfx_current_identifier);
4149 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4150 artwork.snd_current_identifier);
4151 Debug("setup:LoadArtworkInfo", "music set == %s",
4152 artwork.mus_current_identifier);
4155 sortTreeInfo(&artwork.gfx_first);
4156 sortTreeInfo(&artwork.snd_first);
4157 sortTreeInfo(&artwork.mus_first);
4159 #if ENABLE_UNUSED_CODE
4160 dumpTreeInfo(artwork.gfx_first, 0);
4161 dumpTreeInfo(artwork.snd_first, 0);
4162 dumpTreeInfo(artwork.mus_first, 0);
4166 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4168 ArtworkDirTree *artwork_new = newTreeInfo();
4169 char *top_node_name = "standalone artwork";
4171 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4173 artwork_new->level_group = TRUE;
4175 setString(&artwork_new->identifier, top_node_name);
4176 setString(&artwork_new->name, top_node_name);
4177 setString(&artwork_new->name_sorting, top_node_name);
4179 // create node to link back to current custom artwork directory
4180 createParentTreeInfoNode(artwork_new);
4182 // move existing custom artwork tree into newly created sub-tree
4183 artwork_new->node_group->next = *artwork_node;
4185 // change custom artwork tree to contain only newly created node
4186 *artwork_node = artwork_new;
4189 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4190 ArtworkDirTree *node_parent,
4191 LevelDirTree *level_node,
4192 boolean empty_level_set_mode)
4194 int type = (*artwork_node)->type;
4196 // recursively check all level directories for artwork sub-directories
4200 boolean empty_level_set = (level_node->levels == 0);
4202 // check all tree entries for artwork, but skip parent link entries
4203 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4205 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4206 boolean cached = (artwork_new != NULL);
4210 pushTreeInfo(artwork_node, artwork_new);
4214 TreeInfo *topnode_last = *artwork_node;
4215 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4216 ARTWORK_DIRECTORY(type));
4218 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4220 if (topnode_last != *artwork_node) // check for newly added node
4222 artwork_new = *artwork_node;
4224 setString(&artwork_new->identifier, level_node->subdir);
4225 setString(&artwork_new->name, level_node->name);
4226 setString(&artwork_new->name_sorting, level_node->name_sorting);
4228 artwork_new->sort_priority = level_node->sort_priority;
4229 artwork_new->in_user_dir = level_node->in_user_dir;
4231 update_artworkinfo_cache = TRUE;
4237 // insert artwork info (from old cache or filesystem) into new cache
4238 if (artwork_new != NULL)
4239 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4242 DrawInitTextItem(level_node->name);
4244 if (level_node->node_group != NULL)
4246 TreeInfo *artwork_new = newTreeInfo();
4249 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4251 setTreeInfoToDefaults(artwork_new, type);
4253 artwork_new->level_group = TRUE;
4255 setString(&artwork_new->identifier, level_node->subdir);
4257 if (node_parent == NULL) // check for top tree node
4259 char *top_node_name = (empty_level_set_mode ?
4260 "artwork for certain level sets" :
4261 "artwork included in level sets");
4263 setString(&artwork_new->name, top_node_name);
4264 setString(&artwork_new->name_sorting, top_node_name);
4268 setString(&artwork_new->name, level_node->name);
4269 setString(&artwork_new->name_sorting, level_node->name_sorting);
4272 pushTreeInfo(artwork_node, artwork_new);
4274 // create node to link back to current custom artwork directory
4275 createParentTreeInfoNode(artwork_new);
4277 // recursively step into sub-directory and look for more custom artwork
4278 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4279 level_node->node_group,
4280 empty_level_set_mode);
4282 // if sub-tree has no custom artwork at all, remove it
4283 if (artwork_new->node_group->next == NULL)
4284 removeTreeInfo(artwork_node);
4287 level_node = level_node->next;
4291 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4293 // move peviously loaded artwork tree into separate sub-tree
4294 MoveArtworkInfoIntoSubTree(artwork_node);
4296 // load artwork from level sets into separate sub-trees
4297 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4298 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4300 // add top tree node over all sub-trees and set parent links
4301 *artwork_node = addTopTreeInfoNode(*artwork_node);
4304 void LoadLevelArtworkInfo(void)
4306 print_timestamp_init("LoadLevelArtworkInfo");
4308 DrawInitTextHead("Looking for custom level artwork");
4310 print_timestamp_time("DrawTimeText");
4312 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4313 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4314 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4315 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4316 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4317 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4319 SaveArtworkInfoCache();
4321 print_timestamp_time("SaveArtworkInfoCache");
4323 // needed for reloading level artwork not known at ealier stage
4324 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4325 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4326 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4328 print_timestamp_time("getTreeInfoFromIdentifier");
4330 sortTreeInfo(&artwork.gfx_first);
4331 sortTreeInfo(&artwork.snd_first);
4332 sortTreeInfo(&artwork.mus_first);
4334 print_timestamp_time("sortTreeInfo");
4336 #if ENABLE_UNUSED_CODE
4337 dumpTreeInfo(artwork.gfx_first, 0);
4338 dumpTreeInfo(artwork.snd_first, 0);
4339 dumpTreeInfo(artwork.mus_first, 0);
4342 print_timestamp_done("LoadLevelArtworkInfo");
4345 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4346 char *tree_subdir_new, int type)
4348 if (tree_node_old == NULL)
4350 if (type == TREE_TYPE_LEVEL_DIR)
4352 // get level info tree node of personal user level set
4353 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4355 // this may happen if "setup.internal.create_user_levelset" is FALSE
4356 // or if file "levelinfo.conf" is missing in personal user level set
4357 if (tree_node_old == NULL)
4358 tree_node_old = leveldir_first->node_group;
4362 // get artwork info tree node of first artwork set
4363 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4367 if (tree_dir == NULL)
4368 tree_dir = TREE_USERDIR(type);
4370 if (tree_node_old == NULL ||
4372 tree_subdir_new == NULL) // should not happen
4375 int draw_deactivation_mask = GetDrawDeactivationMask();
4377 // override draw deactivation mask (temporarily disable drawing)
4378 SetDrawDeactivationMask(REDRAW_ALL);
4380 if (type == TREE_TYPE_LEVEL_DIR)
4382 // load new level set config and add it next to first user level set
4383 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4384 tree_node_old->node_parent,
4385 tree_dir, tree_subdir_new);
4389 // load new artwork set config and add it next to first artwork set
4390 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4391 tree_node_old->node_parent,
4392 tree_dir, tree_subdir_new, type);
4395 // set draw deactivation mask to previous value
4396 SetDrawDeactivationMask(draw_deactivation_mask);
4398 // get first node of level or artwork info tree
4399 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4401 // get tree info node of newly added level or artwork set
4402 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4405 if (tree_node_new == NULL) // should not happen
4408 // correct top link and parent node link of newly created tree node
4409 tree_node_new->node_top = tree_node_old->node_top;
4410 tree_node_new->node_parent = tree_node_old->node_parent;
4412 // sort tree info to adjust position of newly added tree set
4413 sortTreeInfo(tree_node_first);
4418 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4419 char *tree_subdir_new, int type)
4421 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4422 Fail("internal tree info structure corrupted -- aborting");
4425 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4427 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4430 char *getArtworkIdentifierForUserLevelSet(int type)
4432 char *classic_artwork_set = getClassicArtworkSet(type);
4434 // check for custom artwork configured in "levelinfo.conf"
4435 char *leveldir_artwork_set =
4436 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4437 boolean has_leveldir_artwork_set =
4438 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4439 classic_artwork_set));
4441 // check for custom artwork in sub-directory "graphics" etc.
4442 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4443 char *leveldir_identifier = leveldir_current->identifier;
4444 boolean has_artwork_subdir =
4445 (getTreeInfoFromIdentifier(artwork_first_node,
4446 leveldir_identifier) != NULL);
4448 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4449 has_artwork_subdir ? leveldir_identifier :
4450 classic_artwork_set);
4453 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4455 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4456 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4457 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4461 ti = getTreeInfoFromIdentifier(artwork_first_node,
4462 ARTWORK_DEFAULT_SUBDIR(type));
4464 Fail("cannot find default graphics -- should not happen");
4470 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4472 char *graphics_set =
4473 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4475 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4477 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4479 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4480 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4481 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4484 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4485 char *level_author, int num_levels)
4487 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4488 char *filename_tmp = getStringCat2(filename, ".tmp");
4490 FILE *file_tmp = NULL;
4491 char line[MAX_LINE_LEN];
4492 boolean success = FALSE;
4493 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4495 // update values in level directory tree
4497 if (level_name != NULL)
4498 setString(&leveldir->name, level_name);
4500 if (level_author != NULL)
4501 setString(&leveldir->author, level_author);
4503 if (num_levels != -1)
4504 leveldir->levels = num_levels;
4506 // update values that depend on other values
4508 setString(&leveldir->name_sorting, leveldir->name);
4510 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4512 // sort order of level sets may have changed
4513 sortTreeInfo(&leveldir_first);
4515 if ((file = fopen(filename, MODE_READ)) &&
4516 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4518 while (fgets(line, MAX_LINE_LEN, file))
4520 if (strPrefix(line, "name:") && level_name != NULL)
4521 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4522 else if (strPrefix(line, "author:") && level_author != NULL)
4523 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4524 else if (strPrefix(line, "levels:") && num_levels != -1)
4525 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4527 fputs(line, file_tmp);
4540 success = (rename(filename_tmp, filename) == 0);
4548 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4549 char *level_author, int num_levels,
4550 boolean use_artwork_set)
4552 LevelDirTree *level_info;
4557 // create user level sub-directory, if needed
4558 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4560 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4562 if (!(file = fopen(filename, MODE_WRITE)))
4564 Warn("cannot write level info file '%s'", filename);
4571 level_info = newTreeInfo();
4573 // always start with reliable default values
4574 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4576 setString(&level_info->name, level_name);
4577 setString(&level_info->author, level_author);
4578 level_info->levels = num_levels;
4579 level_info->first_level = 1;
4580 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4581 level_info->readonly = FALSE;
4583 if (use_artwork_set)
4585 level_info->graphics_set =
4586 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4587 level_info->sounds_set =
4588 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4589 level_info->music_set =
4590 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4593 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4595 fprintFileHeader(file, LEVELINFO_FILENAME);
4598 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4600 if (i == LEVELINFO_TOKEN_NAME ||
4601 i == LEVELINFO_TOKEN_AUTHOR ||
4602 i == LEVELINFO_TOKEN_LEVELS ||
4603 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4604 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4605 i == LEVELINFO_TOKEN_READONLY ||
4606 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4607 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4608 i == LEVELINFO_TOKEN_MUSIC_SET)))
4609 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4611 // just to make things nicer :)
4612 if (i == LEVELINFO_TOKEN_AUTHOR ||
4613 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4614 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4615 fprintf(file, "\n");
4618 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4622 SetFilePermissions(filename, PERMS_PRIVATE);
4624 freeTreeInfo(level_info);
4630 static void SaveUserLevelInfo(void)
4632 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4635 char *getSetupValue(int type, void *value)
4637 static char value_string[MAX_LINE_LEN];
4645 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4649 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4653 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4654 *(int *)value == FALSE ? "off" : "on"));
4658 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4661 case TYPE_YES_NO_AUTO:
4662 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4663 *(int *)value == FALSE ? "no" : "yes"));
4667 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4671 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4675 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4679 sprintf(value_string, "%d", *(int *)value);
4683 if (*(char **)value == NULL)
4686 strcpy(value_string, *(char **)value);
4690 sprintf(value_string, "player_%d", *(int *)value + 1);
4694 value_string[0] = '\0';
4698 if (type & TYPE_GHOSTED)
4699 strcpy(value_string, "n/a");
4701 return value_string;
4704 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4708 static char token_string[MAX_LINE_LEN];
4709 int token_type = token_info[token_nr].type;
4710 void *setup_value = token_info[token_nr].value;
4711 char *token_text = token_info[token_nr].text;
4712 char *value_string = getSetupValue(token_type, setup_value);
4714 // build complete token string
4715 sprintf(token_string, "%s%s", prefix, token_text);
4717 // build setup entry line
4718 line = getFormattedSetupEntry(token_string, value_string);
4720 if (token_type == TYPE_KEY_X11)
4722 Key key = *(Key *)setup_value;
4723 char *keyname = getKeyNameFromKey(key);
4725 // add comment, if useful
4726 if (!strEqual(keyname, "(undefined)") &&
4727 !strEqual(keyname, "(unknown)"))
4729 // add at least one whitespace
4731 for (i = strlen(line); i < token_comment_position; i++)
4735 strcat(line, keyname);
4742 static void InitLastPlayedLevels_ParentNode(void)
4744 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4745 LevelDirTree *leveldir_new = NULL;
4747 // check if parent node for last played levels already exists
4748 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4751 leveldir_new = newTreeInfo();
4753 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4755 leveldir_new->level_group = TRUE;
4756 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4758 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4759 setString(&leveldir_new->name, "<< (last played level sets)");
4760 setString(&leveldir_new->name_sorting, leveldir_new->name);
4762 pushTreeInfo(leveldir_top, leveldir_new);
4764 // create node to link back to current level directory
4765 createParentTreeInfoNode(leveldir_new);
4768 void UpdateLastPlayedLevels_TreeInfo(void)
4770 char **last_level_series = setup.level_setup.last_level_series;
4771 LevelDirTree *leveldir_last;
4772 TreeInfo **node_new = NULL;
4775 if (last_level_series[0] == NULL)
4778 InitLastPlayedLevels_ParentNode();
4780 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4781 TOKEN_STR_LAST_LEVEL_SERIES,
4782 TREE_NODE_TYPE_GROUP);
4783 if (leveldir_last == NULL)
4786 node_new = &leveldir_last->node_group->next;
4788 freeTreeInfo(*node_new);
4792 for (i = 0; last_level_series[i] != NULL; i++)
4794 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4795 last_level_series[i]);
4796 if (node_last == NULL)
4799 *node_new = getTreeInfoCopy(node_last); // copy complete node
4801 (*node_new)->node_top = &leveldir_first; // correct top node link
4802 (*node_new)->node_parent = leveldir_last; // correct parent node link
4804 (*node_new)->is_copy = TRUE; // mark entry as node copy
4806 (*node_new)->node_group = NULL;
4807 (*node_new)->next = NULL;
4809 (*node_new)->cl_first = -1; // force setting tree cursor
4811 node_new = &((*node_new)->next);
4815 static void UpdateLastPlayedLevels_List(void)
4817 char **last_level_series = setup.level_setup.last_level_series;
4818 int pos = MAX_LEVELDIR_HISTORY - 1;
4821 // search for potentially already existing entry in list of level sets
4822 for (i = 0; last_level_series[i] != NULL; i++)
4823 if (strEqual(last_level_series[i], leveldir_current->identifier))
4826 // move list of level sets one entry down (using potentially free entry)
4827 for (i = pos; i > 0; i--)
4828 setString(&last_level_series[i], last_level_series[i - 1]);
4830 // put last played level set at top position
4831 setString(&last_level_series[0], leveldir_current->identifier);
4834 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4836 static char *identifier = NULL;
4840 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4842 return NULL; // not used
4846 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4848 TREE_NODE_TYPE_COPY);
4849 return (node_new != NULL ? node_new : node);
4853 void StoreLastPlayedLevels(TreeInfo *node)
4855 StoreOrRestoreLastPlayedLevels(node, TRUE);
4858 void RestoreLastPlayedLevels(TreeInfo **node)
4860 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4863 void LoadLevelSetup_LastSeries(void)
4865 // --------------------------------------------------------------------------
4866 // ~/.<program>/levelsetup.conf
4867 // --------------------------------------------------------------------------
4869 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4870 SetupFileHash *level_setup_hash = NULL;
4874 // always start with reliable default values
4875 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4877 // start with empty history of last played level sets
4878 setString(&setup.level_setup.last_level_series[0], NULL);
4880 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4882 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4884 if (leveldir_current == NULL)
4885 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4888 if ((level_setup_hash = loadSetupFileHash(filename)))
4890 char *last_level_series =
4891 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4893 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4895 if (leveldir_current == NULL)
4896 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4898 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4900 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4901 LevelDirTree *leveldir_last;
4903 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4905 last_level_series = getHashEntry(level_setup_hash, token);
4907 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4909 if (leveldir_last != NULL)
4910 setString(&setup.level_setup.last_level_series[pos++],
4914 setString(&setup.level_setup.last_level_series[pos], NULL);
4916 freeSetupFileHash(level_setup_hash);
4920 Debug("setup", "using default setup values");
4926 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4928 // --------------------------------------------------------------------------
4929 // ~/.<program>/levelsetup.conf
4930 // --------------------------------------------------------------------------
4932 // check if the current level directory structure is available at this point
4933 if (leveldir_current == NULL)
4936 char **last_level_series = setup.level_setup.last_level_series;
4937 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4941 InitUserDataDirectory();
4943 UpdateLastPlayedLevels_List();
4945 if (!(file = fopen(filename, MODE_WRITE)))
4947 Warn("cannot write setup file '%s'", filename);
4954 fprintFileHeader(file, LEVELSETUP_FILENAME);
4956 if (deactivate_last_level_series)
4957 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4959 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4960 leveldir_current->identifier));
4962 for (i = 0; last_level_series[i] != NULL; i++)
4964 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4966 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4968 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4973 SetFilePermissions(filename, PERMS_PRIVATE);
4978 void SaveLevelSetup_LastSeries(void)
4980 SaveLevelSetup_LastSeries_Ext(FALSE);
4983 void SaveLevelSetup_LastSeries_Deactivate(void)
4985 SaveLevelSetup_LastSeries_Ext(TRUE);
4988 static void checkSeriesInfo(void)
4990 static char *level_directory = NULL;
4993 DirectoryEntry *dir_entry;
4996 checked_free(level_directory);
4998 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5000 level_directory = getPath2((leveldir_current->in_user_dir ?
5001 getUserLevelDir(NULL) :
5002 options.level_directory),
5003 leveldir_current->fullpath);
5005 if ((dir = openDirectory(level_directory)) == NULL)
5007 Warn("cannot read level directory '%s'", level_directory);
5013 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5015 if (strlen(dir_entry->basename) > 4 &&
5016 dir_entry->basename[3] == '.' &&
5017 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5019 char levelnum_str[4];
5022 strncpy(levelnum_str, dir_entry->basename, 3);
5023 levelnum_str[3] = '\0';
5025 levelnum_value = atoi(levelnum_str);
5027 if (levelnum_value < leveldir_current->first_level)
5029 Warn("additional level %d found", levelnum_value);
5031 leveldir_current->first_level = levelnum_value;
5033 else if (levelnum_value > leveldir_current->last_level)
5035 Warn("additional level %d found", levelnum_value);
5037 leveldir_current->last_level = levelnum_value;
5043 closeDirectory(dir);
5046 void LoadLevelSetup_SeriesInfo(void)
5049 SetupFileHash *level_setup_hash = NULL;
5050 char *level_subdir = leveldir_current->subdir;
5053 // always start with reliable default values
5054 level_nr = leveldir_current->first_level;
5056 for (i = 0; i < MAX_LEVELS; i++)
5058 LevelStats_setPlayed(i, 0);
5059 LevelStats_setSolved(i, 0);
5064 // --------------------------------------------------------------------------
5065 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5066 // --------------------------------------------------------------------------
5068 level_subdir = leveldir_current->subdir;
5070 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5072 if ((level_setup_hash = loadSetupFileHash(filename)))
5076 // get last played level in this level set
5078 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5082 level_nr = atoi(token_value);
5084 if (level_nr < leveldir_current->first_level)
5085 level_nr = leveldir_current->first_level;
5086 if (level_nr > leveldir_current->last_level)
5087 level_nr = leveldir_current->last_level;
5090 // get handicap level in this level set
5092 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5096 int level_nr = atoi(token_value);
5098 if (level_nr < leveldir_current->first_level)
5099 level_nr = leveldir_current->first_level;
5100 if (level_nr > leveldir_current->last_level + 1)
5101 level_nr = leveldir_current->last_level;
5103 if (leveldir_current->user_defined || !leveldir_current->handicap)
5104 level_nr = leveldir_current->last_level;
5106 leveldir_current->handicap_level = level_nr;
5109 // get number of played and solved levels in this level set
5111 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5113 char *token = HASH_ITERATION_TOKEN(itr);
5114 char *value = HASH_ITERATION_VALUE(itr);
5116 if (strlen(token) == 3 &&
5117 token[0] >= '0' && token[0] <= '9' &&
5118 token[1] >= '0' && token[1] <= '9' &&
5119 token[2] >= '0' && token[2] <= '9')
5121 int level_nr = atoi(token);
5124 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5126 value = strchr(value, ' ');
5129 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5132 END_HASH_ITERATION(hash, itr)
5134 freeSetupFileHash(level_setup_hash);
5138 Debug("setup", "using default setup values");
5144 void SaveLevelSetup_SeriesInfo(void)
5147 char *level_subdir = leveldir_current->subdir;
5148 char *level_nr_str = int2str(level_nr, 0);
5149 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5153 // --------------------------------------------------------------------------
5154 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5155 // --------------------------------------------------------------------------
5157 InitLevelSetupDirectory(level_subdir);
5159 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5161 if (!(file = fopen(filename, MODE_WRITE)))
5163 Warn("cannot write setup file '%s'", filename);
5170 fprintFileHeader(file, LEVELSETUP_FILENAME);
5172 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5174 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5175 handicap_level_str));
5177 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5180 if (LevelStats_getPlayed(i) > 0 ||
5181 LevelStats_getSolved(i) > 0)
5186 sprintf(token, "%03d", i);
5187 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5189 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5195 SetFilePermissions(filename, PERMS_PRIVATE);
5200 int LevelStats_getPlayed(int nr)
5202 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5205 int LevelStats_getSolved(int nr)
5207 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5210 void LevelStats_setPlayed(int nr, int value)
5212 if (nr >= 0 && nr < MAX_LEVELS)
5213 level_stats[nr].played = value;
5216 void LevelStats_setSolved(int nr, int value)
5218 if (nr >= 0 && nr < MAX_LEVELS)
5219 level_stats[nr].solved = value;
5222 void LevelStats_incPlayed(int nr)
5224 if (nr >= 0 && nr < MAX_LEVELS)
5225 level_stats[nr].played++;
5228 void LevelStats_incSolved(int nr)
5230 if (nr >= 0 && nr < MAX_LEVELS)
5231 level_stats[nr].solved++;
5234 void LoadUserSetup(void)
5236 // --------------------------------------------------------------------------
5237 // ~/.<program>/usersetup.conf
5238 // --------------------------------------------------------------------------
5240 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5241 SetupFileHash *user_setup_hash = NULL;
5243 // always start with reliable default values
5246 if ((user_setup_hash = loadSetupFileHash(filename)))
5250 // get last selected user number
5251 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5254 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5256 freeSetupFileHash(user_setup_hash);
5260 Debug("setup", "using default setup values");
5266 void SaveUserSetup(void)
5268 // --------------------------------------------------------------------------
5269 // ~/.<program>/usersetup.conf
5270 // --------------------------------------------------------------------------
5272 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5275 InitMainUserDataDirectory();
5277 if (!(file = fopen(filename, MODE_WRITE)))
5279 Warn("cannot write setup file '%s'", filename);
5286 fprintFileHeader(file, USERSETUP_FILENAME);
5288 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5292 SetFilePermissions(filename, PERMS_PRIVATE);