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";
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_HANDICAP 27
2640 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2641 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2643 #define NUM_LEVELINFO_TOKENS 30
2645 static LevelDirTree ldi;
2647 static struct TokenInfo levelinfo_tokens[] =
2649 // level directory info
2650 { TYPE_STRING, &ldi.identifier, "identifier" },
2651 { TYPE_STRING, &ldi.name, "name" },
2652 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2653 { TYPE_STRING, &ldi.author, "author" },
2654 { TYPE_STRING, &ldi.year, "year" },
2655 { TYPE_STRING, &ldi.program_title, "program_title" },
2656 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2657 { TYPE_STRING, &ldi.program_company, "program_company" },
2658 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2659 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2660 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2661 { TYPE_INTEGER, &ldi.levels, "levels" },
2662 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2663 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2664 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2665 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2666 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2667 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2668 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2669 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2670 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2671 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2672 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2673 { TYPE_STRING, &ldi.music_set, "music_set" },
2674 { TYPE_STRING, &ldi.level_filename, "filename" },
2675 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2676 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2677 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2678 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2679 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2682 static struct TokenInfo artworkinfo_tokens[] =
2684 // artwork directory info
2685 { TYPE_STRING, &ldi.identifier, "identifier" },
2686 { TYPE_STRING, &ldi.subdir, "subdir" },
2687 { TYPE_STRING, &ldi.name, "name" },
2688 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2689 { TYPE_STRING, &ldi.author, "author" },
2690 { TYPE_STRING, &ldi.program_title, "program_title" },
2691 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2692 { TYPE_STRING, &ldi.program_company, "program_company" },
2693 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2694 { TYPE_STRING, &ldi.basepath, "basepath" },
2695 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2696 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2697 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2702 static char *optional_tokens[] =
2705 "program_copyright",
2711 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2715 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2716 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2717 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2718 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2721 ti->node_parent = NULL;
2722 ti->node_group = NULL;
2729 ti->fullpath = NULL;
2730 ti->basepath = NULL;
2731 ti->identifier = NULL;
2732 ti->name = getStringCopy(ANONYMOUS_NAME);
2733 ti->name_sorting = NULL;
2734 ti->author = getStringCopy(ANONYMOUS_NAME);
2737 ti->program_title = NULL;
2738 ti->program_copyright = NULL;
2739 ti->program_company = NULL;
2741 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2742 ti->latest_engine = FALSE; // default: get from level
2743 ti->parent_link = FALSE;
2744 ti->is_copy = FALSE;
2745 ti->in_user_dir = FALSE;
2746 ti->user_defined = FALSE;
2748 ti->class_desc = NULL;
2750 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2752 if (ti->type == TREE_TYPE_LEVEL_DIR)
2754 ti->imported_from = NULL;
2755 ti->imported_by = NULL;
2756 ti->tested_by = NULL;
2758 ti->graphics_set_ecs = NULL;
2759 ti->graphics_set_aga = NULL;
2760 ti->graphics_set = NULL;
2761 ti->sounds_set_default = NULL;
2762 ti->sounds_set_lowpass = NULL;
2763 ti->sounds_set = NULL;
2764 ti->music_set = NULL;
2765 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2766 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2767 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2769 ti->level_filename = NULL;
2770 ti->level_filetype = NULL;
2772 ti->special_flags = NULL;
2775 ti->first_level = 0;
2777 ti->level_group = FALSE;
2778 ti->handicap_level = 0;
2779 ti->readonly = TRUE;
2780 ti->handicap = TRUE;
2781 ti->skip_levels = FALSE;
2783 ti->use_emc_tiles = FALSE;
2787 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2791 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2793 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2798 // copy all values from the parent structure
2800 ti->type = parent->type;
2802 ti->node_top = parent->node_top;
2803 ti->node_parent = parent;
2804 ti->node_group = NULL;
2811 ti->fullpath = NULL;
2812 ti->basepath = NULL;
2813 ti->identifier = NULL;
2814 ti->name = getStringCopy(ANONYMOUS_NAME);
2815 ti->name_sorting = NULL;
2816 ti->author = getStringCopy(parent->author);
2817 ti->year = getStringCopy(parent->year);
2819 ti->program_title = getStringCopy(parent->program_title);
2820 ti->program_copyright = getStringCopy(parent->program_copyright);
2821 ti->program_company = getStringCopy(parent->program_company);
2823 ti->sort_priority = parent->sort_priority;
2824 ti->latest_engine = parent->latest_engine;
2825 ti->parent_link = FALSE;
2826 ti->is_copy = FALSE;
2827 ti->in_user_dir = parent->in_user_dir;
2828 ti->user_defined = parent->user_defined;
2829 ti->color = parent->color;
2830 ti->class_desc = getStringCopy(parent->class_desc);
2832 ti->infotext = getStringCopy(parent->infotext);
2834 if (ti->type == TREE_TYPE_LEVEL_DIR)
2836 ti->imported_from = getStringCopy(parent->imported_from);
2837 ti->imported_by = getStringCopy(parent->imported_by);
2838 ti->tested_by = getStringCopy(parent->tested_by);
2840 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2841 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2842 ti->graphics_set = getStringCopy(parent->graphics_set);
2843 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2844 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2845 ti->sounds_set = getStringCopy(parent->sounds_set);
2846 ti->music_set = getStringCopy(parent->music_set);
2847 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2848 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2849 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2851 ti->level_filename = getStringCopy(parent->level_filename);
2852 ti->level_filetype = getStringCopy(parent->level_filetype);
2854 ti->special_flags = getStringCopy(parent->special_flags);
2856 ti->levels = parent->levels;
2857 ti->first_level = parent->first_level;
2858 ti->last_level = parent->last_level;
2859 ti->level_group = FALSE;
2860 ti->handicap_level = parent->handicap_level;
2861 ti->readonly = parent->readonly;
2862 ti->handicap = parent->handicap;
2863 ti->skip_levels = parent->skip_levels;
2865 ti->use_emc_tiles = parent->use_emc_tiles;
2869 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2871 TreeInfo *ti_copy = newTreeInfo();
2873 // copy all values from the original structure
2875 ti_copy->type = ti->type;
2877 ti_copy->node_top = ti->node_top;
2878 ti_copy->node_parent = ti->node_parent;
2879 ti_copy->node_group = ti->node_group;
2880 ti_copy->next = ti->next;
2882 ti_copy->cl_first = ti->cl_first;
2883 ti_copy->cl_cursor = ti->cl_cursor;
2885 ti_copy->subdir = getStringCopy(ti->subdir);
2886 ti_copy->fullpath = getStringCopy(ti->fullpath);
2887 ti_copy->basepath = getStringCopy(ti->basepath);
2888 ti_copy->identifier = getStringCopy(ti->identifier);
2889 ti_copy->name = getStringCopy(ti->name);
2890 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2891 ti_copy->author = getStringCopy(ti->author);
2892 ti_copy->year = getStringCopy(ti->year);
2894 ti_copy->program_title = getStringCopy(ti->program_title);
2895 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2896 ti_copy->program_company = getStringCopy(ti->program_company);
2898 ti_copy->imported_from = getStringCopy(ti->imported_from);
2899 ti_copy->imported_by = getStringCopy(ti->imported_by);
2900 ti_copy->tested_by = getStringCopy(ti->tested_by);
2902 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2903 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2904 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2905 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2906 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2907 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2908 ti_copy->music_set = getStringCopy(ti->music_set);
2909 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2910 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2911 ti_copy->music_path = getStringCopy(ti->music_path);
2913 ti_copy->level_filename = getStringCopy(ti->level_filename);
2914 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2916 ti_copy->special_flags = getStringCopy(ti->special_flags);
2918 ti_copy->levels = ti->levels;
2919 ti_copy->first_level = ti->first_level;
2920 ti_copy->last_level = ti->last_level;
2921 ti_copy->sort_priority = ti->sort_priority;
2923 ti_copy->latest_engine = ti->latest_engine;
2925 ti_copy->level_group = ti->level_group;
2926 ti_copy->parent_link = ti->parent_link;
2927 ti_copy->is_copy = ti->is_copy;
2928 ti_copy->in_user_dir = ti->in_user_dir;
2929 ti_copy->user_defined = ti->user_defined;
2930 ti_copy->readonly = ti->readonly;
2931 ti_copy->handicap = ti->handicap;
2932 ti_copy->skip_levels = ti->skip_levels;
2934 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2936 ti_copy->color = ti->color;
2937 ti_copy->class_desc = getStringCopy(ti->class_desc);
2938 ti_copy->handicap_level = ti->handicap_level;
2940 ti_copy->infotext = getStringCopy(ti->infotext);
2945 void freeTreeInfo(TreeInfo *ti)
2950 checked_free(ti->subdir);
2951 checked_free(ti->fullpath);
2952 checked_free(ti->basepath);
2953 checked_free(ti->identifier);
2955 checked_free(ti->name);
2956 checked_free(ti->name_sorting);
2957 checked_free(ti->author);
2958 checked_free(ti->year);
2960 checked_free(ti->program_title);
2961 checked_free(ti->program_copyright);
2962 checked_free(ti->program_company);
2964 checked_free(ti->class_desc);
2966 checked_free(ti->infotext);
2968 if (ti->type == TREE_TYPE_LEVEL_DIR)
2970 checked_free(ti->imported_from);
2971 checked_free(ti->imported_by);
2972 checked_free(ti->tested_by);
2974 checked_free(ti->graphics_set_ecs);
2975 checked_free(ti->graphics_set_aga);
2976 checked_free(ti->graphics_set);
2977 checked_free(ti->sounds_set_default);
2978 checked_free(ti->sounds_set_lowpass);
2979 checked_free(ti->sounds_set);
2980 checked_free(ti->music_set);
2982 checked_free(ti->graphics_path);
2983 checked_free(ti->sounds_path);
2984 checked_free(ti->music_path);
2986 checked_free(ti->level_filename);
2987 checked_free(ti->level_filetype);
2989 checked_free(ti->special_flags);
2992 // recursively free child node
2994 freeTreeInfo(ti->node_group);
2996 // recursively free next node
2998 freeTreeInfo(ti->next);
3003 void setSetupInfo(struct TokenInfo *token_info,
3004 int token_nr, char *token_value)
3006 int token_type = token_info[token_nr].type;
3007 void *setup_value = token_info[token_nr].value;
3009 if (token_value == NULL)
3012 // set setup field to corresponding token value
3017 *(boolean *)setup_value = get_boolean_from_string(token_value);
3021 *(int *)setup_value = get_switch3_from_string(token_value);
3025 *(Key *)setup_value = getKeyFromKeyName(token_value);
3029 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3033 *(int *)setup_value = get_integer_from_string(token_value);
3037 checked_free(*(char **)setup_value);
3038 *(char **)setup_value = getStringCopy(token_value);
3042 *(int *)setup_value = get_player_nr_from_string(token_value);
3050 static int compareTreeInfoEntries(const void *object1, const void *object2)
3052 const TreeInfo *entry1 = *((TreeInfo **)object1);
3053 const TreeInfo *entry2 = *((TreeInfo **)object2);
3054 int tree_sorting1 = TREE_SORTING(entry1);
3055 int tree_sorting2 = TREE_SORTING(entry2);
3057 if (tree_sorting1 != tree_sorting2)
3058 return (tree_sorting1 - tree_sorting2);
3060 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3063 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3067 if (node_parent == NULL)
3070 ti_new = newTreeInfo();
3071 setTreeInfoToDefaults(ti_new, node_parent->type);
3073 ti_new->node_parent = node_parent;
3074 ti_new->parent_link = TRUE;
3076 setString(&ti_new->identifier, node_parent->identifier);
3077 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3078 setString(&ti_new->name_sorting, ti_new->name);
3080 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3081 setString(&ti_new->fullpath, node_parent->fullpath);
3083 ti_new->sort_priority = LEVELCLASS_PARENT;
3084 ti_new->latest_engine = node_parent->latest_engine;
3086 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3088 pushTreeInfo(&node_parent->node_group, ti_new);
3093 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3095 if (node_first == NULL)
3098 TreeInfo *ti_new = newTreeInfo();
3099 int type = node_first->type;
3101 setTreeInfoToDefaults(ti_new, type);
3103 ti_new->node_parent = NULL;
3104 ti_new->parent_link = FALSE;
3106 setString(&ti_new->identifier, "top_tree_node");
3107 setString(&ti_new->name, TREE_INFOTEXT(type));
3108 setString(&ti_new->name_sorting, ti_new->name);
3110 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3111 setString(&ti_new->fullpath, ".");
3113 ti_new->sort_priority = LEVELCLASS_TOP;
3114 ti_new->latest_engine = node_first->latest_engine;
3116 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3118 ti_new->node_group = node_first;
3119 ti_new->level_group = TRUE;
3121 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3123 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3124 setString(&ti_new2->name_sorting, ti_new2->name);
3129 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3133 if (node->node_group)
3134 setTreeInfoParentNodes(node->node_group, node);
3136 node->node_parent = node_parent;
3142 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3144 // add top tree node with back link node in previous tree
3145 node_first = createTopTreeInfoNode(node_first);
3147 // set all parent links (back links) in complete tree
3148 setTreeInfoParentNodes(node_first, NULL);
3154 // ----------------------------------------------------------------------------
3155 // functions for handling level and custom artwork info cache
3156 // ----------------------------------------------------------------------------
3158 static void LoadArtworkInfoCache(void)
3160 InitCacheDirectory();
3162 if (artworkinfo_cache_old == NULL)
3164 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3166 // try to load artwork info hash from already existing cache file
3167 artworkinfo_cache_old = loadSetupFileHash(filename);
3169 // try to get program version that artwork info cache was written with
3170 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3172 // check program version of artwork info cache against current version
3173 if (!strEqual(version, program.version_string))
3175 freeSetupFileHash(artworkinfo_cache_old);
3177 artworkinfo_cache_old = NULL;
3180 // if no artwork info cache file was found, start with empty hash
3181 if (artworkinfo_cache_old == NULL)
3182 artworkinfo_cache_old = newSetupFileHash();
3187 if (artworkinfo_cache_new == NULL)
3188 artworkinfo_cache_new = newSetupFileHash();
3190 update_artworkinfo_cache = FALSE;
3193 static void SaveArtworkInfoCache(void)
3195 if (!update_artworkinfo_cache)
3198 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3200 InitCacheDirectory();
3202 saveSetupFileHash(artworkinfo_cache_new, filename);
3207 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3209 static char *prefix = NULL;
3211 checked_free(prefix);
3213 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3218 // (identical to above function, but separate string buffer needed -- nasty)
3219 static char *getCacheToken(char *prefix, char *suffix)
3221 static char *token = NULL;
3223 checked_free(token);
3225 token = getStringCat2WithSeparator(prefix, suffix, ".");
3230 static char *getFileTimestampString(char *filename)
3232 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3235 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3237 struct stat file_status;
3239 if (timestamp_string == NULL)
3242 if (!fileExists(filename)) // file does not exist
3243 return (atoi(timestamp_string) != 0);
3245 if (stat(filename, &file_status) != 0) // cannot stat file
3248 return (file_status.st_mtime != atoi(timestamp_string));
3251 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3253 char *identifier = level_node->subdir;
3254 char *type_string = ARTWORK_DIRECTORY(type);
3255 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3256 char *token_main = getCacheToken(token_prefix, "CACHED");
3257 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3258 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3259 TreeInfo *artwork_info = NULL;
3261 if (!use_artworkinfo_cache)
3264 if (optional_tokens_hash == NULL)
3268 // create hash from list of optional tokens (for quick access)
3269 optional_tokens_hash = newSetupFileHash();
3270 for (i = 0; optional_tokens[i] != NULL; i++)
3271 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3278 artwork_info = newTreeInfo();
3279 setTreeInfoToDefaults(artwork_info, type);
3281 // set all structure fields according to the token/value pairs
3282 ldi = *artwork_info;
3283 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3285 char *token_suffix = artworkinfo_tokens[i].text;
3286 char *token = getCacheToken(token_prefix, token_suffix);
3287 char *value = getHashEntry(artworkinfo_cache_old, token);
3289 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3291 setSetupInfo(artworkinfo_tokens, i, value);
3293 // check if cache entry for this item is mandatory, but missing
3294 if (value == NULL && !optional)
3296 Warn("missing cache entry '%s'", token);
3302 *artwork_info = ldi;
3307 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3308 LEVELINFO_FILENAME);
3309 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3310 ARTWORKINFO_FILENAME(type));
3312 // check if corresponding "levelinfo.conf" file has changed
3313 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3314 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3316 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3319 // check if corresponding "<artworkinfo>.conf" file has changed
3320 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3321 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3323 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3326 checked_free(filename_levelinfo);
3327 checked_free(filename_artworkinfo);
3330 if (!cached && artwork_info != NULL)
3332 freeTreeInfo(artwork_info);
3337 return artwork_info;
3340 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3341 LevelDirTree *level_node, int type)
3343 char *identifier = level_node->subdir;
3344 char *type_string = ARTWORK_DIRECTORY(type);
3345 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3346 char *token_main = getCacheToken(token_prefix, "CACHED");
3347 boolean set_cache_timestamps = TRUE;
3350 setHashEntry(artworkinfo_cache_new, token_main, "true");
3352 if (set_cache_timestamps)
3354 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3355 LEVELINFO_FILENAME);
3356 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3357 ARTWORKINFO_FILENAME(type));
3358 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3359 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3361 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3362 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3364 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3365 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3367 checked_free(filename_levelinfo);
3368 checked_free(filename_artworkinfo);
3369 checked_free(timestamp_levelinfo);
3370 checked_free(timestamp_artworkinfo);
3373 ldi = *artwork_info;
3374 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3376 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3377 char *value = getSetupValue(artworkinfo_tokens[i].type,
3378 artworkinfo_tokens[i].value);
3380 setHashEntry(artworkinfo_cache_new, token, value);
3385 // ----------------------------------------------------------------------------
3386 // functions for loading level info and custom artwork info
3387 // ----------------------------------------------------------------------------
3389 int GetZipFileTreeType(char *zip_filename)
3391 static char *top_dir_path = NULL;
3392 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3393 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3395 GRAPHICSINFO_FILENAME,
3396 SOUNDSINFO_FILENAME,
3402 checked_free(top_dir_path);
3403 top_dir_path = NULL;
3405 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3407 checked_free(top_dir_conf_filename[j]);
3408 top_dir_conf_filename[j] = NULL;
3411 char **zip_entries = zip_list(zip_filename);
3413 // check if zip file successfully opened
3414 if (zip_entries == NULL || zip_entries[0] == NULL)
3415 return TREE_TYPE_UNDEFINED;
3417 // first zip file entry is expected to be top level directory
3418 char *top_dir = zip_entries[0];
3420 // check if valid top level directory found in zip file
3421 if (!strSuffix(top_dir, "/"))
3422 return TREE_TYPE_UNDEFINED;
3424 // get filenames of valid configuration files in top level directory
3425 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3426 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3428 int tree_type = TREE_TYPE_UNDEFINED;
3431 while (zip_entries[e] != NULL)
3433 // check if every zip file entry is below top level directory
3434 if (!strPrefix(zip_entries[e], top_dir))
3435 return TREE_TYPE_UNDEFINED;
3437 // check if this zip file entry is a valid configuration filename
3438 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3440 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3442 // only exactly one valid configuration file allowed
3443 if (tree_type != TREE_TYPE_UNDEFINED)
3444 return TREE_TYPE_UNDEFINED;
3456 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3459 static char *top_dir_path = NULL;
3460 static char *top_dir_conf_filename = NULL;
3462 checked_free(top_dir_path);
3463 checked_free(top_dir_conf_filename);
3465 top_dir_path = NULL;
3466 top_dir_conf_filename = NULL;
3468 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3469 ARTWORKINFO_FILENAME(tree_type));
3471 // check if valid configuration filename determined
3472 if (conf_basename == NULL || strEqual(conf_basename, ""))
3475 char **zip_entries = zip_list(zip_filename);
3477 // check if zip file successfully opened
3478 if (zip_entries == NULL || zip_entries[0] == NULL)
3481 // first zip file entry is expected to be top level directory
3482 char *top_dir = zip_entries[0];
3484 // check if valid top level directory found in zip file
3485 if (!strSuffix(top_dir, "/"))
3488 // get path of extracted top level directory
3489 top_dir_path = getPath2(directory, top_dir);
3491 // remove trailing directory separator from top level directory path
3492 // (required to be able to check for file and directory in next step)
3493 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3495 // check if zip file's top level directory already exists in target directory
3496 if (fileExists(top_dir_path)) // (checks for file and directory)
3499 // get filename of configuration file in top level directory
3500 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3502 boolean found_top_dir_conf_filename = FALSE;
3505 while (zip_entries[i] != NULL)
3507 // check if every zip file entry is below top level directory
3508 if (!strPrefix(zip_entries[i], top_dir))
3511 // check if this zip file entry is the configuration filename
3512 if (strEqual(zip_entries[i], top_dir_conf_filename))
3513 found_top_dir_conf_filename = TRUE;
3518 // check if valid configuration filename was found in zip file
3519 if (!found_top_dir_conf_filename)
3525 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3528 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3531 if (!zip_file_valid)
3533 Warn("zip file '%s' rejected!", zip_filename);
3538 char **zip_entries = zip_extract(zip_filename, directory);
3540 if (zip_entries == NULL)
3542 Warn("zip file '%s' could not be extracted!", zip_filename);
3547 Info("zip file '%s' successfully extracted!", zip_filename);
3549 // first zip file entry contains top level directory
3550 char *top_dir = zip_entries[0];
3552 // remove trailing directory separator from top level directory
3553 top_dir[strlen(top_dir) - 1] = '\0';
3558 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3561 DirectoryEntry *dir_entry;
3563 if ((dir = openDirectory(directory)) == NULL)
3565 // display error if directory is main "options.graphics_directory" etc.
3566 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3567 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3568 Warn("cannot read directory '%s'", directory);
3573 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3575 // skip non-zip files (and also directories with zip extension)
3576 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3579 char *zip_filename = getPath2(directory, dir_entry->basename);
3580 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3581 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3583 // check if zip file hasn't already been extracted or rejected
3584 if (!fileExists(zip_filename_extracted) &&
3585 !fileExists(zip_filename_rejected))
3587 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3589 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3590 zip_filename_rejected);
3593 // create empty file to mark zip file as extracted or rejected
3594 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3595 fclose(marker_file);
3598 free(zip_filename_extracted);
3599 free(zip_filename_rejected);
3603 closeDirectory(dir);
3606 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3607 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3609 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3610 TreeInfo *node_parent,
3611 char *level_directory,
3612 char *directory_name)
3614 char *directory_path = getPath2(level_directory, directory_name);
3615 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3616 SetupFileHash *setup_file_hash;
3617 LevelDirTree *leveldir_new = NULL;
3620 // unless debugging, silently ignore directories without "levelinfo.conf"
3621 if (!options.debug && !fileExists(filename))
3623 free(directory_path);
3629 setup_file_hash = loadSetupFileHash(filename);
3631 if (setup_file_hash == NULL)
3633 #if DEBUG_NO_CONFIG_FILE
3634 Debug("setup", "ignoring level directory '%s'", directory_path);
3637 free(directory_path);
3643 leveldir_new = newTreeInfo();
3646 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3648 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3650 leveldir_new->subdir = getStringCopy(directory_name);
3652 // set all structure fields according to the token/value pairs
3653 ldi = *leveldir_new;
3654 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3655 setSetupInfo(levelinfo_tokens, i,
3656 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3657 *leveldir_new = ldi;
3659 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3660 setString(&leveldir_new->name, leveldir_new->subdir);
3662 if (leveldir_new->identifier == NULL)
3663 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3665 if (leveldir_new->name_sorting == NULL)
3666 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3668 if (node_parent == NULL) // top level group
3670 leveldir_new->basepath = getStringCopy(level_directory);
3671 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3673 else // sub level group
3675 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3676 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3679 leveldir_new->last_level =
3680 leveldir_new->first_level + leveldir_new->levels - 1;
3682 leveldir_new->in_user_dir =
3683 (!strEqual(leveldir_new->basepath, options.level_directory));
3685 // adjust some settings if user's private level directory was detected
3686 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3687 leveldir_new->in_user_dir &&
3688 (strEqual(leveldir_new->subdir, getLoginName()) ||
3689 strEqual(leveldir_new->name, getLoginName()) ||
3690 strEqual(leveldir_new->author, getRealName())))
3692 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3693 leveldir_new->readonly = FALSE;
3696 leveldir_new->user_defined =
3697 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3699 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3701 leveldir_new->handicap_level = // set handicap to default value
3702 (leveldir_new->user_defined || !leveldir_new->handicap ?
3703 leveldir_new->last_level : leveldir_new->first_level);
3705 DrawInitTextItem(leveldir_new->name);
3707 pushTreeInfo(node_first, leveldir_new);
3709 freeSetupFileHash(setup_file_hash);
3711 if (leveldir_new->level_group)
3713 // create node to link back to current level directory
3714 createParentTreeInfoNode(leveldir_new);
3716 // recursively step into sub-directory and look for more level series
3717 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3718 leveldir_new, directory_path);
3721 free(directory_path);
3727 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3728 TreeInfo *node_parent,
3729 char *level_directory)
3731 // ---------- 1st stage: process any level set zip files ----------
3733 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3735 // ---------- 2nd stage: check for level set directories ----------
3738 DirectoryEntry *dir_entry;
3739 boolean valid_entry_found = FALSE;
3741 if ((dir = openDirectory(level_directory)) == NULL)
3743 Warn("cannot read level directory '%s'", level_directory);
3748 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3750 char *directory_name = dir_entry->basename;
3751 char *directory_path = getPath2(level_directory, directory_name);
3753 // skip entries for current and parent directory
3754 if (strEqual(directory_name, ".") ||
3755 strEqual(directory_name, ".."))
3757 free(directory_path);
3762 // find out if directory entry is itself a directory
3763 if (!dir_entry->is_directory) // not a directory
3765 free(directory_path);
3770 free(directory_path);
3772 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3773 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3774 strEqual(directory_name, MUSIC_DIRECTORY))
3777 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3782 closeDirectory(dir);
3784 // special case: top level directory may directly contain "levelinfo.conf"
3785 if (node_parent == NULL && !valid_entry_found)
3787 // check if this directory directly contains a file "levelinfo.conf"
3788 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3789 level_directory, ".");
3792 if (!valid_entry_found)
3793 Warn("cannot find any valid level series in directory '%s'",
3797 boolean AdjustGraphicsForEMC(void)
3799 boolean settings_changed = FALSE;
3801 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3802 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3804 return settings_changed;
3807 boolean AdjustSoundsForEMC(void)
3809 boolean settings_changed = FALSE;
3811 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3812 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3814 return settings_changed;
3817 void LoadLevelInfo(void)
3819 InitUserLevelDirectory(getLoginName());
3821 DrawInitTextHead("Loading level series");
3823 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3824 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3826 leveldir_first = createTopTreeInfoNode(leveldir_first);
3828 /* after loading all level set information, clone the level directory tree
3829 and remove all level sets without levels (these may still contain artwork
3830 to be offered in the setup menu as "custom artwork", and are therefore
3831 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3832 leveldir_first_all = leveldir_first;
3833 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3835 AdjustGraphicsForEMC();
3836 AdjustSoundsForEMC();
3838 // before sorting, the first entries will be from the user directory
3839 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3841 if (leveldir_first == NULL)
3842 Fail("cannot find any valid level series in any directory");
3844 sortTreeInfo(&leveldir_first);
3846 #if ENABLE_UNUSED_CODE
3847 dumpTreeInfo(leveldir_first, 0);
3851 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3852 TreeInfo *node_parent,
3853 char *base_directory,
3854 char *directory_name, int type)
3856 char *directory_path = getPath2(base_directory, directory_name);
3857 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3858 SetupFileHash *setup_file_hash = NULL;
3859 TreeInfo *artwork_new = NULL;
3862 if (fileExists(filename))
3863 setup_file_hash = loadSetupFileHash(filename);
3865 if (setup_file_hash == NULL) // no config file -- look for artwork files
3868 DirectoryEntry *dir_entry;
3869 boolean valid_file_found = FALSE;
3871 if ((dir = openDirectory(directory_path)) != NULL)
3873 while ((dir_entry = readDirectory(dir)) != NULL)
3875 if (FileIsArtworkType(dir_entry->filename, type))
3877 valid_file_found = TRUE;
3883 closeDirectory(dir);
3886 if (!valid_file_found)
3888 #if DEBUG_NO_CONFIG_FILE
3889 if (!strEqual(directory_name, "."))
3890 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3893 free(directory_path);
3900 artwork_new = newTreeInfo();
3903 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3905 setTreeInfoToDefaults(artwork_new, type);
3907 artwork_new->subdir = getStringCopy(directory_name);
3909 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3911 // set all structure fields according to the token/value pairs
3913 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3914 setSetupInfo(levelinfo_tokens, i,
3915 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3918 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3919 setString(&artwork_new->name, artwork_new->subdir);
3921 if (artwork_new->identifier == NULL)
3922 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3924 if (artwork_new->name_sorting == NULL)
3925 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3928 if (node_parent == NULL) // top level group
3930 artwork_new->basepath = getStringCopy(base_directory);
3931 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3933 else // sub level group
3935 artwork_new->basepath = getStringCopy(node_parent->basepath);
3936 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3939 artwork_new->in_user_dir =
3940 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3942 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3944 if (setup_file_hash == NULL) // (after determining ".user_defined")
3946 if (strEqual(artwork_new->subdir, "."))
3948 if (artwork_new->user_defined)
3950 setString(&artwork_new->identifier, "private");
3951 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3955 setString(&artwork_new->identifier, "classic");
3956 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3959 setString(&artwork_new->class_desc,
3960 getLevelClassDescription(artwork_new));
3964 setString(&artwork_new->identifier, artwork_new->subdir);
3967 setString(&artwork_new->name, artwork_new->identifier);
3968 setString(&artwork_new->name_sorting, artwork_new->name);
3971 pushTreeInfo(node_first, artwork_new);
3973 freeSetupFileHash(setup_file_hash);
3975 free(directory_path);
3981 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3982 TreeInfo *node_parent,
3983 char *base_directory, int type)
3985 // ---------- 1st stage: process any artwork set zip files ----------
3987 ProcessZipFilesInDirectory(base_directory, type);
3989 // ---------- 2nd stage: check for artwork set directories ----------
3992 DirectoryEntry *dir_entry;
3993 boolean valid_entry_found = FALSE;
3995 if ((dir = openDirectory(base_directory)) == NULL)
3997 // display error if directory is main "options.graphics_directory" etc.
3998 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3999 Warn("cannot read directory '%s'", base_directory);
4004 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4006 char *directory_name = dir_entry->basename;
4007 char *directory_path = getPath2(base_directory, directory_name);
4009 // skip directory entries for current and parent directory
4010 if (strEqual(directory_name, ".") ||
4011 strEqual(directory_name, ".."))
4013 free(directory_path);
4018 // skip directory entries which are not a directory
4019 if (!dir_entry->is_directory) // not a directory
4021 free(directory_path);
4026 free(directory_path);
4028 // check if this directory contains artwork with or without config file
4029 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4031 directory_name, type);
4034 closeDirectory(dir);
4036 // check if this directory directly contains artwork itself
4037 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4038 base_directory, ".",
4040 if (!valid_entry_found)
4041 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4044 static TreeInfo *getDummyArtworkInfo(int type)
4046 // this is only needed when there is completely no artwork available
4047 TreeInfo *artwork_new = newTreeInfo();
4049 setTreeInfoToDefaults(artwork_new, type);
4051 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4052 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4053 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4055 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4056 setString(&artwork_new->name, UNDEFINED_FILENAME);
4057 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4062 void SetCurrentArtwork(int type)
4064 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4065 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4066 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4067 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4069 // set current artwork to artwork configured in setup menu
4070 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4072 // if not found, set current artwork to default artwork
4073 if (*current_ptr == NULL)
4074 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4076 // if not found, set current artwork to first artwork in tree
4077 if (*current_ptr == NULL)
4078 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4081 void ChangeCurrentArtworkIfNeeded(int type)
4083 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4084 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4086 if (!strEqual(current_identifier, setup_set))
4087 SetCurrentArtwork(type);
4090 void LoadArtworkInfo(void)
4092 LoadArtworkInfoCache();
4094 DrawInitTextHead("Looking for custom artwork");
4096 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4097 options.graphics_directory,
4098 TREE_TYPE_GRAPHICS_DIR);
4099 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4100 getUserGraphicsDir(),
4101 TREE_TYPE_GRAPHICS_DIR);
4103 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4104 options.sounds_directory,
4105 TREE_TYPE_SOUNDS_DIR);
4106 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4108 TREE_TYPE_SOUNDS_DIR);
4110 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4111 options.music_directory,
4112 TREE_TYPE_MUSIC_DIR);
4113 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4115 TREE_TYPE_MUSIC_DIR);
4117 if (artwork.gfx_first == NULL)
4118 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4119 if (artwork.snd_first == NULL)
4120 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4121 if (artwork.mus_first == NULL)
4122 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4124 // before sorting, the first entries will be from the user directory
4125 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4126 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4127 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4129 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4130 artwork.snd_current_identifier = artwork.snd_current->identifier;
4131 artwork.mus_current_identifier = artwork.mus_current->identifier;
4133 #if ENABLE_UNUSED_CODE
4134 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4135 artwork.gfx_current_identifier);
4136 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4137 artwork.snd_current_identifier);
4138 Debug("setup:LoadArtworkInfo", "music set == %s",
4139 artwork.mus_current_identifier);
4142 sortTreeInfo(&artwork.gfx_first);
4143 sortTreeInfo(&artwork.snd_first);
4144 sortTreeInfo(&artwork.mus_first);
4146 #if ENABLE_UNUSED_CODE
4147 dumpTreeInfo(artwork.gfx_first, 0);
4148 dumpTreeInfo(artwork.snd_first, 0);
4149 dumpTreeInfo(artwork.mus_first, 0);
4153 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4155 ArtworkDirTree *artwork_new = newTreeInfo();
4156 char *top_node_name = "standalone artwork";
4158 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4160 artwork_new->level_group = TRUE;
4162 setString(&artwork_new->identifier, top_node_name);
4163 setString(&artwork_new->name, top_node_name);
4164 setString(&artwork_new->name_sorting, top_node_name);
4166 // create node to link back to current custom artwork directory
4167 createParentTreeInfoNode(artwork_new);
4169 // move existing custom artwork tree into newly created sub-tree
4170 artwork_new->node_group->next = *artwork_node;
4172 // change custom artwork tree to contain only newly created node
4173 *artwork_node = artwork_new;
4176 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4177 ArtworkDirTree *node_parent,
4178 LevelDirTree *level_node,
4179 boolean empty_level_set_mode)
4181 int type = (*artwork_node)->type;
4183 // recursively check all level directories for artwork sub-directories
4187 boolean empty_level_set = (level_node->levels == 0);
4189 // check all tree entries for artwork, but skip parent link entries
4190 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4192 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4193 boolean cached = (artwork_new != NULL);
4197 pushTreeInfo(artwork_node, artwork_new);
4201 TreeInfo *topnode_last = *artwork_node;
4202 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4203 ARTWORK_DIRECTORY(type));
4205 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4207 if (topnode_last != *artwork_node) // check for newly added node
4209 artwork_new = *artwork_node;
4211 setString(&artwork_new->identifier, level_node->subdir);
4212 setString(&artwork_new->name, level_node->name);
4213 setString(&artwork_new->name_sorting, level_node->name_sorting);
4215 artwork_new->sort_priority = level_node->sort_priority;
4216 artwork_new->in_user_dir = level_node->in_user_dir;
4218 update_artworkinfo_cache = TRUE;
4224 // insert artwork info (from old cache or filesystem) into new cache
4225 if (artwork_new != NULL)
4226 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4229 DrawInitTextItem(level_node->name);
4231 if (level_node->node_group != NULL)
4233 TreeInfo *artwork_new = newTreeInfo();
4236 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4238 setTreeInfoToDefaults(artwork_new, type);
4240 artwork_new->level_group = TRUE;
4242 setString(&artwork_new->identifier, level_node->subdir);
4244 if (node_parent == NULL) // check for top tree node
4246 char *top_node_name = (empty_level_set_mode ?
4247 "artwork for certain level sets" :
4248 "artwork included in level sets");
4250 setString(&artwork_new->name, top_node_name);
4251 setString(&artwork_new->name_sorting, top_node_name);
4255 setString(&artwork_new->name, level_node->name);
4256 setString(&artwork_new->name_sorting, level_node->name_sorting);
4259 pushTreeInfo(artwork_node, artwork_new);
4261 // create node to link back to current custom artwork directory
4262 createParentTreeInfoNode(artwork_new);
4264 // recursively step into sub-directory and look for more custom artwork
4265 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4266 level_node->node_group,
4267 empty_level_set_mode);
4269 // if sub-tree has no custom artwork at all, remove it
4270 if (artwork_new->node_group->next == NULL)
4271 removeTreeInfo(artwork_node);
4274 level_node = level_node->next;
4278 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4280 // move peviously loaded artwork tree into separate sub-tree
4281 MoveArtworkInfoIntoSubTree(artwork_node);
4283 // load artwork from level sets into separate sub-trees
4284 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4285 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4287 // add top tree node over all sub-trees and set parent links
4288 *artwork_node = addTopTreeInfoNode(*artwork_node);
4291 void LoadLevelArtworkInfo(void)
4293 print_timestamp_init("LoadLevelArtworkInfo");
4295 DrawInitTextHead("Looking for custom level artwork");
4297 print_timestamp_time("DrawTimeText");
4299 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4300 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4301 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4302 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4303 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4304 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4306 SaveArtworkInfoCache();
4308 print_timestamp_time("SaveArtworkInfoCache");
4310 // needed for reloading level artwork not known at ealier stage
4311 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4312 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4313 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4315 print_timestamp_time("getTreeInfoFromIdentifier");
4317 sortTreeInfo(&artwork.gfx_first);
4318 sortTreeInfo(&artwork.snd_first);
4319 sortTreeInfo(&artwork.mus_first);
4321 print_timestamp_time("sortTreeInfo");
4323 #if ENABLE_UNUSED_CODE
4324 dumpTreeInfo(artwork.gfx_first, 0);
4325 dumpTreeInfo(artwork.snd_first, 0);
4326 dumpTreeInfo(artwork.mus_first, 0);
4329 print_timestamp_done("LoadLevelArtworkInfo");
4332 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4333 char *tree_subdir_new, int type)
4335 if (tree_node_old == NULL)
4337 if (type == TREE_TYPE_LEVEL_DIR)
4339 // get level info tree node of personal user level set
4340 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4342 // this may happen if "setup.internal.create_user_levelset" is FALSE
4343 // or if file "levelinfo.conf" is missing in personal user level set
4344 if (tree_node_old == NULL)
4345 tree_node_old = leveldir_first->node_group;
4349 // get artwork info tree node of first artwork set
4350 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4354 if (tree_dir == NULL)
4355 tree_dir = TREE_USERDIR(type);
4357 if (tree_node_old == NULL ||
4359 tree_subdir_new == NULL) // should not happen
4362 int draw_deactivation_mask = GetDrawDeactivationMask();
4364 // override draw deactivation mask (temporarily disable drawing)
4365 SetDrawDeactivationMask(REDRAW_ALL);
4367 if (type == TREE_TYPE_LEVEL_DIR)
4369 // load new level set config and add it next to first user level set
4370 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4371 tree_node_old->node_parent,
4372 tree_dir, tree_subdir_new);
4376 // load new artwork set config and add it next to first artwork set
4377 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4378 tree_node_old->node_parent,
4379 tree_dir, tree_subdir_new, type);
4382 // set draw deactivation mask to previous value
4383 SetDrawDeactivationMask(draw_deactivation_mask);
4385 // get first node of level or artwork info tree
4386 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4388 // get tree info node of newly added level or artwork set
4389 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4392 if (tree_node_new == NULL) // should not happen
4395 // correct top link and parent node link of newly created tree node
4396 tree_node_new->node_top = tree_node_old->node_top;
4397 tree_node_new->node_parent = tree_node_old->node_parent;
4399 // sort tree info to adjust position of newly added tree set
4400 sortTreeInfo(tree_node_first);
4405 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4406 char *tree_subdir_new, int type)
4408 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4409 Fail("internal tree info structure corrupted -- aborting");
4412 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4414 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4417 char *getArtworkIdentifierForUserLevelSet(int type)
4419 char *classic_artwork_set = getClassicArtworkSet(type);
4421 // check for custom artwork configured in "levelinfo.conf"
4422 char *leveldir_artwork_set =
4423 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4424 boolean has_leveldir_artwork_set =
4425 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4426 classic_artwork_set));
4428 // check for custom artwork in sub-directory "graphics" etc.
4429 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4430 char *leveldir_identifier = leveldir_current->identifier;
4431 boolean has_artwork_subdir =
4432 (getTreeInfoFromIdentifier(artwork_first_node,
4433 leveldir_identifier) != NULL);
4435 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4436 has_artwork_subdir ? leveldir_identifier :
4437 classic_artwork_set);
4440 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4442 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4443 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4444 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4448 ti = getTreeInfoFromIdentifier(artwork_first_node,
4449 ARTWORK_DEFAULT_SUBDIR(type));
4451 Fail("cannot find default graphics -- should not happen");
4457 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4459 char *graphics_set =
4460 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4462 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4464 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4466 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4467 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4468 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4471 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4472 char *level_author, int num_levels)
4474 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4475 char *filename_tmp = getStringCat2(filename, ".tmp");
4477 FILE *file_tmp = NULL;
4478 char line[MAX_LINE_LEN];
4479 boolean success = FALSE;
4480 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4482 // update values in level directory tree
4484 if (level_name != NULL)
4485 setString(&leveldir->name, level_name);
4487 if (level_author != NULL)
4488 setString(&leveldir->author, level_author);
4490 if (num_levels != -1)
4491 leveldir->levels = num_levels;
4493 // update values that depend on other values
4495 setString(&leveldir->name_sorting, leveldir->name);
4497 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4499 // sort order of level sets may have changed
4500 sortTreeInfo(&leveldir_first);
4502 if ((file = fopen(filename, MODE_READ)) &&
4503 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4505 while (fgets(line, MAX_LINE_LEN, file))
4507 if (strPrefix(line, "name:") && level_name != NULL)
4508 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4509 else if (strPrefix(line, "author:") && level_author != NULL)
4510 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4511 else if (strPrefix(line, "levels:") && num_levels != -1)
4512 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4514 fputs(line, file_tmp);
4527 success = (rename(filename_tmp, filename) == 0);
4535 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4536 char *level_author, int num_levels,
4537 boolean use_artwork_set)
4539 LevelDirTree *level_info;
4544 // create user level sub-directory, if needed
4545 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4547 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4549 if (!(file = fopen(filename, MODE_WRITE)))
4551 Warn("cannot write level info file '%s'", filename);
4558 level_info = newTreeInfo();
4560 // always start with reliable default values
4561 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4563 setString(&level_info->name, level_name);
4564 setString(&level_info->author, level_author);
4565 level_info->levels = num_levels;
4566 level_info->first_level = 1;
4567 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4568 level_info->readonly = FALSE;
4570 if (use_artwork_set)
4572 level_info->graphics_set =
4573 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4574 level_info->sounds_set =
4575 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4576 level_info->music_set =
4577 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4580 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4582 fprintFileHeader(file, LEVELINFO_FILENAME);
4585 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4587 if (i == LEVELINFO_TOKEN_NAME ||
4588 i == LEVELINFO_TOKEN_AUTHOR ||
4589 i == LEVELINFO_TOKEN_LEVELS ||
4590 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4591 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4592 i == LEVELINFO_TOKEN_READONLY ||
4593 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4594 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4595 i == LEVELINFO_TOKEN_MUSIC_SET)))
4596 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4598 // just to make things nicer :)
4599 if (i == LEVELINFO_TOKEN_AUTHOR ||
4600 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4601 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4602 fprintf(file, "\n");
4605 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4609 SetFilePermissions(filename, PERMS_PRIVATE);
4611 freeTreeInfo(level_info);
4617 static void SaveUserLevelInfo(void)
4619 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4622 char *getSetupValue(int type, void *value)
4624 static char value_string[MAX_LINE_LEN];
4632 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4636 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4640 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4641 *(int *)value == FALSE ? "off" : "on"));
4645 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4648 case TYPE_YES_NO_AUTO:
4649 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4650 *(int *)value == FALSE ? "no" : "yes"));
4654 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4658 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4662 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4666 sprintf(value_string, "%d", *(int *)value);
4670 if (*(char **)value == NULL)
4673 strcpy(value_string, *(char **)value);
4677 sprintf(value_string, "player_%d", *(int *)value + 1);
4681 value_string[0] = '\0';
4685 if (type & TYPE_GHOSTED)
4686 strcpy(value_string, "n/a");
4688 return value_string;
4691 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4695 static char token_string[MAX_LINE_LEN];
4696 int token_type = token_info[token_nr].type;
4697 void *setup_value = token_info[token_nr].value;
4698 char *token_text = token_info[token_nr].text;
4699 char *value_string = getSetupValue(token_type, setup_value);
4701 // build complete token string
4702 sprintf(token_string, "%s%s", prefix, token_text);
4704 // build setup entry line
4705 line = getFormattedSetupEntry(token_string, value_string);
4707 if (token_type == TYPE_KEY_X11)
4709 Key key = *(Key *)setup_value;
4710 char *keyname = getKeyNameFromKey(key);
4712 // add comment, if useful
4713 if (!strEqual(keyname, "(undefined)") &&
4714 !strEqual(keyname, "(unknown)"))
4716 // add at least one whitespace
4718 for (i = strlen(line); i < token_comment_position; i++)
4722 strcat(line, keyname);
4729 static void InitLastPlayedLevels_ParentNode(void)
4731 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4732 LevelDirTree *leveldir_new = NULL;
4734 // check if parent node for last played levels already exists
4735 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4738 leveldir_new = newTreeInfo();
4740 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4742 leveldir_new->level_group = TRUE;
4743 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4745 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4746 setString(&leveldir_new->name, "<< (last played level sets)");
4747 setString(&leveldir_new->name_sorting, leveldir_new->name);
4749 pushTreeInfo(leveldir_top, leveldir_new);
4751 // create node to link back to current level directory
4752 createParentTreeInfoNode(leveldir_new);
4755 void UpdateLastPlayedLevels_TreeInfo(void)
4757 char **last_level_series = setup.level_setup.last_level_series;
4758 LevelDirTree *leveldir_last;
4759 TreeInfo **node_new = NULL;
4762 if (last_level_series[0] == NULL)
4765 InitLastPlayedLevels_ParentNode();
4767 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4768 TOKEN_STR_LAST_LEVEL_SERIES,
4769 TREE_NODE_TYPE_GROUP);
4770 if (leveldir_last == NULL)
4773 node_new = &leveldir_last->node_group->next;
4775 freeTreeInfo(*node_new);
4779 for (i = 0; last_level_series[i] != NULL; i++)
4781 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4782 last_level_series[i]);
4783 if (node_last == NULL)
4786 *node_new = getTreeInfoCopy(node_last); // copy complete node
4788 (*node_new)->node_top = &leveldir_first; // correct top node link
4789 (*node_new)->node_parent = leveldir_last; // correct parent node link
4791 (*node_new)->is_copy = TRUE; // mark entry as node copy
4793 (*node_new)->node_group = NULL;
4794 (*node_new)->next = NULL;
4796 (*node_new)->cl_first = -1; // force setting tree cursor
4798 node_new = &((*node_new)->next);
4802 static void UpdateLastPlayedLevels_List(void)
4804 char **last_level_series = setup.level_setup.last_level_series;
4805 int pos = MAX_LEVELDIR_HISTORY - 1;
4808 // search for potentially already existing entry in list of level sets
4809 for (i = 0; last_level_series[i] != NULL; i++)
4810 if (strEqual(last_level_series[i], leveldir_current->identifier))
4813 // move list of level sets one entry down (using potentially free entry)
4814 for (i = pos; i > 0; i--)
4815 setString(&last_level_series[i], last_level_series[i - 1]);
4817 // put last played level set at top position
4818 setString(&last_level_series[0], leveldir_current->identifier);
4821 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4823 static char *identifier = NULL;
4827 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4829 return NULL; // not used
4833 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4835 TREE_NODE_TYPE_COPY);
4836 return (node_new != NULL ? node_new : node);
4840 void StoreLastPlayedLevels(TreeInfo *node)
4842 StoreOrRestoreLastPlayedLevels(node, TRUE);
4845 void RestoreLastPlayedLevels(TreeInfo **node)
4847 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4850 void LoadLevelSetup_LastSeries(void)
4852 // --------------------------------------------------------------------------
4853 // ~/.<program>/levelsetup.conf
4854 // --------------------------------------------------------------------------
4856 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4857 SetupFileHash *level_setup_hash = NULL;
4861 // always start with reliable default values
4862 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4864 // start with empty history of last played level sets
4865 setString(&setup.level_setup.last_level_series[0], NULL);
4867 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4869 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4871 if (leveldir_current == NULL)
4872 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4875 if ((level_setup_hash = loadSetupFileHash(filename)))
4877 char *last_level_series =
4878 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4880 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4882 if (leveldir_current == NULL)
4883 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4885 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4887 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4888 LevelDirTree *leveldir_last;
4890 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4892 last_level_series = getHashEntry(level_setup_hash, token);
4894 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4896 if (leveldir_last != NULL)
4897 setString(&setup.level_setup.last_level_series[pos++],
4901 setString(&setup.level_setup.last_level_series[pos], NULL);
4903 freeSetupFileHash(level_setup_hash);
4907 Debug("setup", "using default setup values");
4913 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4915 // --------------------------------------------------------------------------
4916 // ~/.<program>/levelsetup.conf
4917 // --------------------------------------------------------------------------
4919 // check if the current level directory structure is available at this point
4920 if (leveldir_current == NULL)
4923 char **last_level_series = setup.level_setup.last_level_series;
4924 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4928 InitUserDataDirectory();
4930 UpdateLastPlayedLevels_List();
4932 if (!(file = fopen(filename, MODE_WRITE)))
4934 Warn("cannot write setup file '%s'", filename);
4941 fprintFileHeader(file, LEVELSETUP_FILENAME);
4943 if (deactivate_last_level_series)
4944 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4946 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4947 leveldir_current->identifier));
4949 for (i = 0; last_level_series[i] != NULL; i++)
4951 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4953 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4955 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4960 SetFilePermissions(filename, PERMS_PRIVATE);
4965 void SaveLevelSetup_LastSeries(void)
4967 SaveLevelSetup_LastSeries_Ext(FALSE);
4970 void SaveLevelSetup_LastSeries_Deactivate(void)
4972 SaveLevelSetup_LastSeries_Ext(TRUE);
4975 static void checkSeriesInfo(void)
4977 static char *level_directory = NULL;
4980 DirectoryEntry *dir_entry;
4983 checked_free(level_directory);
4985 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4987 level_directory = getPath2((leveldir_current->in_user_dir ?
4988 getUserLevelDir(NULL) :
4989 options.level_directory),
4990 leveldir_current->fullpath);
4992 if ((dir = openDirectory(level_directory)) == NULL)
4994 Warn("cannot read level directory '%s'", level_directory);
5000 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5002 if (strlen(dir_entry->basename) > 4 &&
5003 dir_entry->basename[3] == '.' &&
5004 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5006 char levelnum_str[4];
5009 strncpy(levelnum_str, dir_entry->basename, 3);
5010 levelnum_str[3] = '\0';
5012 levelnum_value = atoi(levelnum_str);
5014 if (levelnum_value < leveldir_current->first_level)
5016 Warn("additional level %d found", levelnum_value);
5018 leveldir_current->first_level = levelnum_value;
5020 else if (levelnum_value > leveldir_current->last_level)
5022 Warn("additional level %d found", levelnum_value);
5024 leveldir_current->last_level = levelnum_value;
5030 closeDirectory(dir);
5033 void LoadLevelSetup_SeriesInfo(void)
5036 SetupFileHash *level_setup_hash = NULL;
5037 char *level_subdir = leveldir_current->subdir;
5040 // always start with reliable default values
5041 level_nr = leveldir_current->first_level;
5043 for (i = 0; i < MAX_LEVELS; i++)
5045 LevelStats_setPlayed(i, 0);
5046 LevelStats_setSolved(i, 0);
5051 // --------------------------------------------------------------------------
5052 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5053 // --------------------------------------------------------------------------
5055 level_subdir = leveldir_current->subdir;
5057 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5059 if ((level_setup_hash = loadSetupFileHash(filename)))
5063 // get last played level in this level set
5065 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5069 level_nr = atoi(token_value);
5071 if (level_nr < leveldir_current->first_level)
5072 level_nr = leveldir_current->first_level;
5073 if (level_nr > leveldir_current->last_level)
5074 level_nr = leveldir_current->last_level;
5077 // get handicap level in this level set
5079 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5083 int level_nr = atoi(token_value);
5085 if (level_nr < leveldir_current->first_level)
5086 level_nr = leveldir_current->first_level;
5087 if (level_nr > leveldir_current->last_level + 1)
5088 level_nr = leveldir_current->last_level;
5090 if (leveldir_current->user_defined || !leveldir_current->handicap)
5091 level_nr = leveldir_current->last_level;
5093 leveldir_current->handicap_level = level_nr;
5096 // get number of played and solved levels in this level set
5098 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5100 char *token = HASH_ITERATION_TOKEN(itr);
5101 char *value = HASH_ITERATION_VALUE(itr);
5103 if (strlen(token) == 3 &&
5104 token[0] >= '0' && token[0] <= '9' &&
5105 token[1] >= '0' && token[1] <= '9' &&
5106 token[2] >= '0' && token[2] <= '9')
5108 int level_nr = atoi(token);
5111 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5113 value = strchr(value, ' ');
5116 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5119 END_HASH_ITERATION(hash, itr)
5121 freeSetupFileHash(level_setup_hash);
5125 Debug("setup", "using default setup values");
5131 void SaveLevelSetup_SeriesInfo(void)
5134 char *level_subdir = leveldir_current->subdir;
5135 char *level_nr_str = int2str(level_nr, 0);
5136 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5140 // --------------------------------------------------------------------------
5141 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5142 // --------------------------------------------------------------------------
5144 InitLevelSetupDirectory(level_subdir);
5146 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5148 if (!(file = fopen(filename, MODE_WRITE)))
5150 Warn("cannot write setup file '%s'", filename);
5157 fprintFileHeader(file, LEVELSETUP_FILENAME);
5159 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5161 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5162 handicap_level_str));
5164 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5167 if (LevelStats_getPlayed(i) > 0 ||
5168 LevelStats_getSolved(i) > 0)
5173 sprintf(token, "%03d", i);
5174 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5176 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5182 SetFilePermissions(filename, PERMS_PRIVATE);
5187 int LevelStats_getPlayed(int nr)
5189 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5192 int LevelStats_getSolved(int nr)
5194 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5197 void LevelStats_setPlayed(int nr, int value)
5199 if (nr >= 0 && nr < MAX_LEVELS)
5200 level_stats[nr].played = value;
5203 void LevelStats_setSolved(int nr, int value)
5205 if (nr >= 0 && nr < MAX_LEVELS)
5206 level_stats[nr].solved = value;
5209 void LevelStats_incPlayed(int nr)
5211 if (nr >= 0 && nr < MAX_LEVELS)
5212 level_stats[nr].played++;
5215 void LevelStats_incSolved(int nr)
5217 if (nr >= 0 && nr < MAX_LEVELS)
5218 level_stats[nr].solved++;
5221 void LoadUserSetup(void)
5223 // --------------------------------------------------------------------------
5224 // ~/.<program>/usersetup.conf
5225 // --------------------------------------------------------------------------
5227 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5228 SetupFileHash *user_setup_hash = NULL;
5230 // always start with reliable default values
5233 if ((user_setup_hash = loadSetupFileHash(filename)))
5237 // get last selected user number
5238 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5241 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5243 freeSetupFileHash(user_setup_hash);
5247 Debug("setup", "using default setup values");
5253 void SaveUserSetup(void)
5255 // --------------------------------------------------------------------------
5256 // ~/.<program>/usersetup.conf
5257 // --------------------------------------------------------------------------
5259 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5262 InitMainUserDataDirectory();
5264 if (!(file = fopen(filename, MODE_WRITE)))
5266 Warn("cannot write setup file '%s'", filename);
5273 fprintFileHeader(file, USERSETUP_FILENAME);
5275 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5279 SetFilePermissions(filename, PERMS_PRIVATE);