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 *getCorrectedArtworkBasename(char *basename)
892 char *getCustomImageFilename(char *basename)
894 static char *filename = NULL;
895 boolean skip_setup_artwork = FALSE;
897 checked_free(filename);
899 basename = getCorrectedArtworkBasename(basename);
901 if (!gfx.override_level_graphics)
903 // 1st try: look for special artwork in current level series directory
904 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
905 if (fileExists(filename))
910 // check if there is special artwork configured in level series config
911 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
913 // 2nd try: look for special artwork configured in level series config
914 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
915 if (fileExists(filename))
920 // take missing artwork configured in level set config from default
921 skip_setup_artwork = TRUE;
925 if (!skip_setup_artwork)
927 // 3rd try: look for special artwork in configured artwork directory
928 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
929 if (fileExists(filename))
935 // 4th try: look for default artwork in new default artwork directory
936 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
937 if (fileExists(filename))
942 // 5th try: look for default artwork in old default artwork directory
943 filename = getImg2(options.graphics_directory, basename);
944 if (fileExists(filename))
947 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
951 Warn("cannot find artwork file '%s' (using fallback)", basename);
953 // 6th try: look for fallback artwork in old default artwork directory
954 // (needed to prevent errors when trying to access unused artwork files)
955 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
956 if (fileExists(filename))
960 return NULL; // cannot find specified artwork file anywhere
963 char *getCustomSoundFilename(char *basename)
965 static char *filename = NULL;
966 boolean skip_setup_artwork = FALSE;
968 checked_free(filename);
970 basename = getCorrectedArtworkBasename(basename);
972 if (!gfx.override_level_sounds)
974 // 1st try: look for special artwork in current level series directory
975 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
976 if (fileExists(filename))
981 // check if there is special artwork configured in level series config
982 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
984 // 2nd try: look for special artwork configured in level series config
985 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
986 if (fileExists(filename))
991 // take missing artwork configured in level set config from default
992 skip_setup_artwork = TRUE;
996 if (!skip_setup_artwork)
998 // 3rd try: look for special artwork in configured artwork directory
999 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1000 if (fileExists(filename))
1006 // 4th try: look for default artwork in new default artwork directory
1007 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1008 if (fileExists(filename))
1013 // 5th try: look for default artwork in old default artwork directory
1014 filename = getPath2(options.sounds_directory, basename);
1015 if (fileExists(filename))
1018 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1022 Warn("cannot find artwork file '%s' (using fallback)", basename);
1024 // 6th try: look for fallback artwork in old default artwork directory
1025 // (needed to prevent errors when trying to access unused artwork files)
1026 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1027 if (fileExists(filename))
1031 return NULL; // cannot find specified artwork file anywhere
1034 char *getCustomMusicFilename(char *basename)
1036 static char *filename = NULL;
1037 boolean skip_setup_artwork = FALSE;
1039 checked_free(filename);
1041 basename = getCorrectedArtworkBasename(basename);
1043 if (!gfx.override_level_music)
1045 // 1st try: look for special artwork in current level series directory
1046 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1047 if (fileExists(filename))
1052 // check if there is special artwork configured in level series config
1053 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1055 // 2nd try: look for special artwork configured in level series config
1056 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1057 if (fileExists(filename))
1062 // take missing artwork configured in level set config from default
1063 skip_setup_artwork = TRUE;
1067 if (!skip_setup_artwork)
1069 // 3rd try: look for special artwork in configured artwork directory
1070 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1071 if (fileExists(filename))
1077 // 4th try: look for default artwork in new default artwork directory
1078 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1079 if (fileExists(filename))
1084 // 5th try: look for default artwork in old default artwork directory
1085 filename = getPath2(options.music_directory, basename);
1086 if (fileExists(filename))
1089 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1093 Warn("cannot find artwork file '%s' (using fallback)", basename);
1095 // 6th try: look for fallback artwork in old default artwork directory
1096 // (needed to prevent errors when trying to access unused artwork files)
1097 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1098 if (fileExists(filename))
1102 return NULL; // cannot find specified artwork file anywhere
1105 char *getCustomArtworkFilename(char *basename, int type)
1107 if (type == ARTWORK_TYPE_GRAPHICS)
1108 return getCustomImageFilename(basename);
1109 else if (type == ARTWORK_TYPE_SOUNDS)
1110 return getCustomSoundFilename(basename);
1111 else if (type == ARTWORK_TYPE_MUSIC)
1112 return getCustomMusicFilename(basename);
1114 return UNDEFINED_FILENAME;
1117 char *getCustomArtworkConfigFilename(int type)
1119 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1122 char *getCustomArtworkLevelConfigFilename(int type)
1124 static char *filename = NULL;
1126 checked_free(filename);
1128 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1133 char *getCustomMusicDirectory(void)
1135 static char *directory = NULL;
1136 boolean skip_setup_artwork = FALSE;
1138 checked_free(directory);
1140 if (!gfx.override_level_music)
1142 // 1st try: look for special artwork in current level series directory
1143 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1144 if (directoryExists(directory))
1149 // check if there is special artwork configured in level series config
1150 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1152 // 2nd try: look for special artwork configured in level series config
1153 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1154 if (directoryExists(directory))
1159 // take missing artwork configured in level set config from default
1160 skip_setup_artwork = TRUE;
1164 if (!skip_setup_artwork)
1166 // 3rd try: look for special artwork in configured artwork directory
1167 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1168 if (directoryExists(directory))
1174 // 4th try: look for default artwork in new default artwork directory
1175 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1176 if (directoryExists(directory))
1181 // 5th try: look for default artwork in old default artwork directory
1182 directory = getStringCopy(options.music_directory);
1183 if (directoryExists(directory))
1186 return NULL; // cannot find specified artwork file anywhere
1189 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1191 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1193 touchFile(filename);
1195 checked_free(filename);
1198 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1200 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1204 checked_free(filename);
1207 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1209 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1210 boolean success = fileExists(filename);
1212 checked_free(filename);
1217 void InitTapeDirectory(char *level_subdir)
1219 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1221 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1222 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
1223 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
1226 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1229 void InitScoreDirectory(char *level_subdir)
1231 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1232 createDirectory(getScoreDir(NULL), "main score", PERMS_PRIVATE);
1233 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PRIVATE);
1236 void InitScoreCacheDirectory(char *level_subdir)
1238 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1239 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1240 createDirectory(getScoreCacheDir(NULL), "main score", PERMS_PRIVATE);
1241 createDirectory(getScoreCacheDir(level_subdir), "level score", PERMS_PRIVATE);
1244 void InitScoreTapeDirectory(char *level_subdir, int nr)
1246 InitScoreDirectory(level_subdir);
1248 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape", PERMS_PRIVATE);
1251 static void SaveUserLevelInfo(void);
1253 void InitUserLevelDirectory(char *level_subdir)
1255 if (!directoryExists(getUserLevelDir(level_subdir)))
1257 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1258 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
1259 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
1261 if (setup.internal.create_user_levelset)
1262 SaveUserLevelInfo();
1266 void InitNetworkLevelDirectory(char *level_subdir)
1268 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1270 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1271 createDirectory(getNetworkDir(), "network data", PERMS_PRIVATE);
1272 createDirectory(getNetworkLevelDir(NULL), "main network level", PERMS_PRIVATE);
1273 createDirectory(getNetworkLevelDir(level_subdir), "network level", PERMS_PRIVATE);
1277 void InitLevelSetupDirectory(char *level_subdir)
1279 createDirectory(getUserGameDataDir(), "user data", PERMS_PRIVATE);
1280 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
1281 createDirectory(getLevelSetupDir(level_subdir), "level setup", PERMS_PRIVATE);
1284 static void InitCacheDirectory(void)
1286 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1287 createDirectory(getCacheDir(), "cache data", PERMS_PRIVATE);
1291 // ----------------------------------------------------------------------------
1292 // some functions to handle lists of level and artwork directories
1293 // ----------------------------------------------------------------------------
1295 TreeInfo *newTreeInfo(void)
1297 return checked_calloc(sizeof(TreeInfo));
1300 TreeInfo *newTreeInfo_setDefaults(int type)
1302 TreeInfo *ti = newTreeInfo();
1304 setTreeInfoToDefaults(ti, type);
1309 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1311 node_new->next = *node_first;
1312 *node_first = node_new;
1315 void removeTreeInfo(TreeInfo **node_first)
1317 TreeInfo *node_old = *node_first;
1319 *node_first = node_old->next;
1320 node_old->next = NULL;
1322 freeTreeInfo(node_old);
1325 int numTreeInfo(TreeInfo *node)
1338 boolean validLevelSeries(TreeInfo *node)
1340 // in a number of cases, tree node is no valid level set
1341 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1347 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1349 if (validLevelSeries(node))
1351 else if (node->is_copy)
1352 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1354 return getFirstValidTreeInfoEntry(default_node);
1357 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1362 if (node->node_group) // enter node group (step down into tree)
1363 return getFirstValidTreeInfoEntry(node->node_group);
1365 if (node->parent_link) // skip first node (back link) of node group
1366 get_next_node = TRUE;
1368 if (!get_next_node) // get current regular tree node
1371 // get next regular tree node, or step up until one is found
1372 while (node->next == NULL && node->node_parent != NULL)
1373 node = node->node_parent;
1375 return getFirstValidTreeInfoEntry(node->next);
1378 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1380 return getValidTreeInfoEntryExt(node, FALSE);
1383 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1385 return getValidTreeInfoEntryExt(node, TRUE);
1388 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1393 if (node->node_parent == NULL) // top level group
1394 return *node->node_top;
1395 else // sub level group
1396 return node->node_parent->node_group;
1399 int numTreeInfoInGroup(TreeInfo *node)
1401 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1404 int getPosFromTreeInfo(TreeInfo *node)
1406 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1411 if (node_cmp == node)
1415 node_cmp = node_cmp->next;
1421 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1423 TreeInfo *node_default = node;
1435 return node_default;
1438 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1439 int node_type_wanted)
1441 if (identifier == NULL)
1446 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1447 strEqual(identifier, node->identifier))
1450 if (node->node_group)
1452 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1465 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1467 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1470 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1471 TreeInfo *node, boolean skip_sets_without_levels)
1478 if (!node->parent_link && !node->level_group &&
1479 skip_sets_without_levels && node->levels == 0)
1480 return cloneTreeNode(node_top, node_parent, node->next,
1481 skip_sets_without_levels);
1483 node_new = getTreeInfoCopy(node); // copy complete node
1485 node_new->node_top = node_top; // correct top node link
1486 node_new->node_parent = node_parent; // correct parent node link
1488 if (node->level_group)
1489 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1490 skip_sets_without_levels);
1492 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1493 skip_sets_without_levels);
1498 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1500 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1502 *ti_new = ti_cloned;
1505 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1507 boolean settings_changed = FALSE;
1511 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1512 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1513 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1514 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1515 char *graphics_set = NULL;
1517 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1518 graphics_set = node->graphics_set_ecs;
1520 if (node->graphics_set_aga && (want_aga || has_only_aga))
1521 graphics_set = node->graphics_set_aga;
1523 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1525 setString(&node->graphics_set, graphics_set);
1526 settings_changed = TRUE;
1529 if (node->node_group != NULL)
1530 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1535 return settings_changed;
1538 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1540 boolean settings_changed = FALSE;
1544 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1545 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1546 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1547 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1548 char *sounds_set = NULL;
1550 if (node->sounds_set_default && (want_default || has_only_default))
1551 sounds_set = node->sounds_set_default;
1553 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1554 sounds_set = node->sounds_set_lowpass;
1556 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1558 setString(&node->sounds_set, sounds_set);
1559 settings_changed = TRUE;
1562 if (node->node_group != NULL)
1563 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1568 return settings_changed;
1571 int dumpTreeInfo(TreeInfo *node, int depth)
1573 char bullet_list[] = { '-', '*', 'o' };
1574 int num_leaf_nodes = 0;
1578 Debug("tree", "Dumping TreeInfo:");
1582 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1584 for (i = 0; i < depth * 2; i++)
1585 DebugContinued("", " ");
1587 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1588 bullet, node->name, node->identifier,
1589 (node->node_parent ? node->node_parent->identifier : "-"),
1590 (node->node_group ? "[GROUP]" :
1591 node->is_copy ? "[COPY]" : ""));
1593 if (!node->node_group && !node->parent_link)
1597 // use for dumping artwork info tree
1598 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1599 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1602 if (node->node_group != NULL)
1603 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1609 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1611 return num_leaf_nodes;
1614 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1615 int (*compare_function)(const void *,
1618 int num_nodes = numTreeInfo(*node_first);
1619 TreeInfo **sort_array;
1620 TreeInfo *node = *node_first;
1626 // allocate array for sorting structure pointers
1627 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1629 // writing structure pointers to sorting array
1630 while (i < num_nodes && node) // double boundary check...
1632 sort_array[i] = node;
1638 // sorting the structure pointers in the sorting array
1639 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1642 // update the linkage of list elements with the sorted node array
1643 for (i = 0; i < num_nodes - 1; i++)
1644 sort_array[i]->next = sort_array[i + 1];
1645 sort_array[num_nodes - 1]->next = NULL;
1647 // update the linkage of the main list anchor pointer
1648 *node_first = sort_array[0];
1652 // now recursively sort the level group structures
1656 if (node->node_group != NULL)
1657 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1663 void sortTreeInfo(TreeInfo **node_first)
1665 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1669 // ============================================================================
1670 // some stuff from "files.c"
1671 // ============================================================================
1673 #if defined(PLATFORM_WIN32)
1675 #define S_IRGRP S_IRUSR
1678 #define S_IROTH S_IRUSR
1681 #define S_IWGRP S_IWUSR
1684 #define S_IWOTH S_IWUSR
1687 #define S_IXGRP S_IXUSR
1690 #define S_IXOTH S_IXUSR
1693 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1698 #endif // PLATFORM_WIN32
1700 // file permissions for newly written files
1701 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1702 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1703 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1705 #define MODE_W_PRIVATE (S_IWUSR)
1706 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1707 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1709 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1710 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1711 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1713 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1714 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1715 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1718 char *getHomeDir(void)
1720 static char *dir = NULL;
1722 #if defined(PLATFORM_WIN32)
1725 dir = checked_malloc(MAX_PATH + 1);
1727 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1730 #elif defined(PLATFORM_EMSCRIPTEN)
1731 dir = "/persistent";
1732 #elif defined(PLATFORM_UNIX)
1735 if ((dir = getenv("HOME")) == NULL)
1737 dir = getUnixHomeDir();
1740 dir = getStringCopy(dir);
1752 char *getPersonalDataDir(void)
1754 static char *personal_data_dir = NULL;
1756 #if defined(PLATFORM_MACOSX)
1757 if (personal_data_dir == NULL)
1758 personal_data_dir = getPath2(getHomeDir(), "Documents");
1760 if (personal_data_dir == NULL)
1761 personal_data_dir = getHomeDir();
1764 return personal_data_dir;
1767 char *getMainUserGameDataDir(void)
1769 static char *main_user_data_dir = NULL;
1771 #if defined(PLATFORM_ANDROID)
1772 if (main_user_data_dir == NULL)
1773 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1774 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1775 SDL_AndroidGetExternalStoragePath() :
1776 SDL_AndroidGetInternalStoragePath());
1778 if (main_user_data_dir == NULL)
1779 main_user_data_dir = getPath2(getPersonalDataDir(),
1780 program.userdata_subdir);
1783 return main_user_data_dir;
1786 char *getUserGameDataDir(void)
1789 return getMainUserGameDataDir();
1791 return getUserDir(user.nr);
1794 char *getSetupDir(void)
1796 return getUserGameDataDir();
1799 static mode_t posix_umask(mode_t mask)
1801 #if defined(PLATFORM_UNIX)
1808 static int posix_mkdir(const char *pathname, mode_t mode)
1810 #if defined(PLATFORM_WIN32)
1811 return mkdir(pathname);
1813 return mkdir(pathname, mode);
1817 static boolean posix_process_running_setgid(void)
1819 #if defined(PLATFORM_UNIX)
1820 return (getgid() != getegid());
1826 void createDirectory(char *dir, char *text, int permission_class)
1828 if (directoryExists(dir))
1831 // leave "other" permissions in umask untouched, but ensure group parts
1832 // of USERDATA_DIR_MODE are not masked
1833 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1834 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1835 mode_t last_umask = posix_umask(0);
1836 mode_t group_umask = ~(dir_mode & S_IRWXG);
1837 int running_setgid = posix_process_running_setgid();
1839 if (permission_class == PERMS_PUBLIC)
1841 // if we're setgid, protect files against "other"
1842 // else keep umask(0) to make the dir world-writable
1845 posix_umask(last_umask & group_umask);
1847 dir_mode = DIR_PERMS_PUBLIC_ALL;
1850 if (posix_mkdir(dir, dir_mode) != 0)
1851 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1853 if (permission_class == PERMS_PUBLIC && !running_setgid)
1854 chmod(dir, dir_mode);
1856 posix_umask(last_umask); // restore previous umask
1859 void InitMainUserDataDirectory(void)
1861 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1864 void InitUserDataDirectory(void)
1866 createDirectory(getMainUserGameDataDir(), "main user data", PERMS_PRIVATE);
1870 createDirectory(getUserDir(-1), "users", PERMS_PRIVATE);
1871 createDirectory(getUserDir(user.nr), "user data", PERMS_PRIVATE);
1875 void SetFilePermissions(char *filename, int permission_class)
1877 int running_setgid = posix_process_running_setgid();
1878 int perms = (permission_class == PERMS_PRIVATE ?
1879 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1881 if (permission_class == PERMS_PUBLIC && !running_setgid)
1882 perms = FILE_PERMS_PUBLIC_ALL;
1884 chmod(filename, perms);
1887 char *getCookie(char *file_type)
1889 static char cookie[MAX_COOKIE_LEN + 1];
1891 if (strlen(program.cookie_prefix) + 1 +
1892 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1893 return "[COOKIE ERROR]"; // should never happen
1895 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1896 program.cookie_prefix, file_type,
1897 program.version_super, program.version_major);
1902 void fprintFileHeader(FILE *file, char *basename)
1904 char *prefix = "# ";
1907 fprintf_line_with_prefix(file, prefix, sep1, 77);
1908 fprintf(file, "%s%s\n", prefix, basename);
1909 fprintf_line_with_prefix(file, prefix, sep1, 77);
1910 fprintf(file, "\n");
1913 int getFileVersionFromCookieString(const char *cookie)
1915 const char *ptr_cookie1, *ptr_cookie2;
1916 const char *pattern1 = "_FILE_VERSION_";
1917 const char *pattern2 = "?.?";
1918 const int len_cookie = strlen(cookie);
1919 const int len_pattern1 = strlen(pattern1);
1920 const int len_pattern2 = strlen(pattern2);
1921 const int len_pattern = len_pattern1 + len_pattern2;
1922 int version_super, version_major;
1924 if (len_cookie <= len_pattern)
1927 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1928 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1930 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1933 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1934 ptr_cookie2[1] != '.' ||
1935 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1938 version_super = ptr_cookie2[0] - '0';
1939 version_major = ptr_cookie2[2] - '0';
1941 return VERSION_IDENT(version_super, version_major, 0, 0);
1944 boolean checkCookieString(const char *cookie, const char *template)
1946 const char *pattern = "_FILE_VERSION_?.?";
1947 const int len_cookie = strlen(cookie);
1948 const int len_template = strlen(template);
1949 const int len_pattern = strlen(pattern);
1951 if (len_cookie != len_template)
1954 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1961 // ----------------------------------------------------------------------------
1962 // setup file list and hash handling functions
1963 // ----------------------------------------------------------------------------
1965 char *getFormattedSetupEntry(char *token, char *value)
1968 static char entry[MAX_LINE_LEN];
1970 // if value is an empty string, just return token without value
1974 // start with the token and some spaces to format output line
1975 sprintf(entry, "%s:", token);
1976 for (i = strlen(entry); i < token_value_position; i++)
1979 // continue with the token's value
1980 strcat(entry, value);
1985 SetupFileList *newSetupFileList(char *token, char *value)
1987 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1989 new->token = getStringCopy(token);
1990 new->value = getStringCopy(value);
1997 void freeSetupFileList(SetupFileList *list)
2002 checked_free(list->token);
2003 checked_free(list->value);
2006 freeSetupFileList(list->next);
2011 char *getListEntry(SetupFileList *list, char *token)
2016 if (strEqual(list->token, token))
2019 return getListEntry(list->next, token);
2022 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2027 if (strEqual(list->token, token))
2029 checked_free(list->value);
2031 list->value = getStringCopy(value);
2035 else if (list->next == NULL)
2036 return (list->next = newSetupFileList(token, value));
2038 return setListEntry(list->next, token, value);
2041 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2046 if (list->next == NULL)
2047 return (list->next = newSetupFileList(token, value));
2049 return addListEntry(list->next, token, value);
2052 #if ENABLE_UNUSED_CODE
2054 static void printSetupFileList(SetupFileList *list)
2059 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2060 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2062 printSetupFileList(list->next);
2068 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2069 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2070 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2071 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2073 #define insert_hash_entry hashtable_insert
2074 #define search_hash_entry hashtable_search
2075 #define change_hash_entry hashtable_change
2076 #define remove_hash_entry hashtable_remove
2079 unsigned int get_hash_from_key(void *key)
2084 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2085 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2086 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2087 it works better than many other constants, prime or not) has never been
2088 adequately explained.
2090 If you just want to have a good hash function, and cannot wait, djb2
2091 is one of the best string hash functions i know. It has excellent
2092 distribution and speed on many different sets of keys and table sizes.
2093 You are not likely to do better with one of the "well known" functions
2094 such as PJW, K&R, etc.
2096 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2099 char *str = (char *)key;
2100 unsigned int hash = 5381;
2103 while ((c = *str++))
2104 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2109 int hash_keys_are_equal(void *key1, void *key2)
2111 return (strEqual((char *)key1, (char *)key2));
2114 SetupFileHash *newSetupFileHash(void)
2116 SetupFileHash *new_hash =
2117 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2119 if (new_hash == NULL)
2120 Fail("create_hashtable() failed -- out of memory");
2125 void freeSetupFileHash(SetupFileHash *hash)
2130 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2133 char *getHashEntry(SetupFileHash *hash, char *token)
2138 return search_hash_entry(hash, token);
2141 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2148 value_copy = getStringCopy(value);
2150 // change value; if it does not exist, insert it as new
2151 if (!change_hash_entry(hash, token, value_copy))
2152 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2153 Fail("cannot insert into hash -- aborting");
2156 char *removeHashEntry(SetupFileHash *hash, char *token)
2161 return remove_hash_entry(hash, token);
2164 #if ENABLE_UNUSED_CODE
2166 static void printSetupFileHash(SetupFileHash *hash)
2168 BEGIN_HASH_ITERATION(hash, itr)
2170 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2171 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2173 END_HASH_ITERATION(hash, itr)
2178 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2179 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2180 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2182 static boolean token_value_separator_found = FALSE;
2183 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2184 static boolean token_value_separator_warning = FALSE;
2186 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2187 static boolean token_already_exists_warning = FALSE;
2190 static boolean getTokenValueFromSetupLineExt(char *line,
2191 char **token_ptr, char **value_ptr,
2192 char *filename, char *line_raw,
2194 boolean separator_required)
2196 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2197 char *token, *value, *line_ptr;
2199 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2200 if (line_raw == NULL)
2202 strncpy(line_copy, line, MAX_LINE_LEN);
2203 line_copy[MAX_LINE_LEN] = '\0';
2206 strcpy(line_raw_copy, line_copy);
2207 line_raw = line_raw_copy;
2210 // cut trailing comment from input line
2211 for (line_ptr = line; *line_ptr; line_ptr++)
2213 if (*line_ptr == '#')
2220 // cut trailing whitespaces from input line
2221 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2222 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2225 // ignore empty lines
2229 // cut leading whitespaces from token
2230 for (token = line; *token; token++)
2231 if (*token != ' ' && *token != '\t')
2234 // start with empty value as reliable default
2237 token_value_separator_found = FALSE;
2239 // find end of token to determine start of value
2240 for (line_ptr = token; *line_ptr; line_ptr++)
2242 // first look for an explicit token/value separator, like ':' or '='
2243 if (*line_ptr == ':' || *line_ptr == '=')
2245 *line_ptr = '\0'; // terminate token string
2246 value = line_ptr + 1; // set beginning of value
2248 token_value_separator_found = TRUE;
2254 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2255 // fallback: if no token/value separator found, also allow whitespaces
2256 if (!token_value_separator_found && !separator_required)
2258 for (line_ptr = token; *line_ptr; line_ptr++)
2260 if (*line_ptr == ' ' || *line_ptr == '\t')
2262 *line_ptr = '\0'; // terminate token string
2263 value = line_ptr + 1; // set beginning of value
2265 token_value_separator_found = TRUE;
2271 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2272 if (token_value_separator_found)
2274 if (!token_value_separator_warning)
2276 Debug("setup", "---");
2278 if (filename != NULL)
2280 Debug("setup", "missing token/value separator(s) in config file:");
2281 Debug("setup", "- config file: '%s'", filename);
2285 Debug("setup", "missing token/value separator(s):");
2288 token_value_separator_warning = TRUE;
2291 if (filename != NULL)
2292 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2294 Debug("setup", "- line: '%s'", line_raw);
2300 // cut trailing whitespaces from token
2301 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2302 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2305 // cut leading whitespaces from value
2306 for (; *value; value++)
2307 if (*value != ' ' && *value != '\t')
2316 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2318 // while the internal (old) interface does not require a token/value
2319 // separator (for downwards compatibility with existing files which
2320 // don't use them), it is mandatory for the external (new) interface
2322 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2325 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2326 boolean top_recursion_level, boolean is_hash)
2328 static SetupFileHash *include_filename_hash = NULL;
2329 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2330 char *token, *value, *line_ptr;
2331 void *insert_ptr = NULL;
2332 boolean read_continued_line = FALSE;
2334 int line_nr = 0, token_count = 0, include_count = 0;
2336 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2337 token_value_separator_warning = FALSE;
2340 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2341 token_already_exists_warning = FALSE;
2344 if (!(file = openFile(filename, MODE_READ)))
2346 #if DEBUG_NO_CONFIG_FILE
2347 Debug("setup", "cannot open configuration file '%s'", filename);
2353 // use "insert pointer" to store list end for constant insertion complexity
2355 insert_ptr = setup_file_data;
2357 // on top invocation, create hash to mark included files (to prevent loops)
2358 if (top_recursion_level)
2359 include_filename_hash = newSetupFileHash();
2361 // mark this file as already included (to prevent including it again)
2362 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2364 while (!checkEndOfFile(file))
2366 // read next line of input file
2367 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2370 // check if line was completely read and is terminated by line break
2371 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2374 // cut trailing line break (this can be newline and/or carriage return)
2375 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2376 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2379 // copy raw input line for later use (mainly debugging output)
2380 strcpy(line_raw, line);
2382 if (read_continued_line)
2384 // append new line to existing line, if there is enough space
2385 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2386 strcat(previous_line, line_ptr);
2388 strcpy(line, previous_line); // copy storage buffer to line
2390 read_continued_line = FALSE;
2393 // if the last character is '\', continue at next line
2394 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2396 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2397 strcpy(previous_line, line); // copy line to storage buffer
2399 read_continued_line = TRUE;
2404 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2405 line_raw, line_nr, FALSE))
2410 if (strEqual(token, "include"))
2412 if (getHashEntry(include_filename_hash, value) == NULL)
2414 char *basepath = getBasePath(filename);
2415 char *basename = getBaseName(value);
2416 char *filename_include = getPath2(basepath, basename);
2418 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2422 free(filename_include);
2428 Warn("ignoring already processed file '%s'", value);
2435 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2437 getHashEntry((SetupFileHash *)setup_file_data, token);
2439 if (old_value != NULL)
2441 if (!token_already_exists_warning)
2443 Debug("setup", "---");
2444 Debug("setup", "duplicate token(s) found in config file:");
2445 Debug("setup", "- config file: '%s'", filename);
2447 token_already_exists_warning = TRUE;
2450 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2451 Debug("setup", " old value: '%s'", old_value);
2452 Debug("setup", " new value: '%s'", value);
2456 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2460 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2470 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2471 if (token_value_separator_warning)
2472 Debug("setup", "---");
2475 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2476 if (token_already_exists_warning)
2477 Debug("setup", "---");
2480 if (token_count == 0 && include_count == 0)
2481 Warn("configuration file '%s' is empty", filename);
2483 if (top_recursion_level)
2484 freeSetupFileHash(include_filename_hash);
2489 static int compareSetupFileData(const void *object1, const void *object2)
2491 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2492 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2494 return strcmp(entry1->token, entry2->token);
2497 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2499 int item_count = hashtable_count(hash);
2500 int item_size = sizeof(struct ConfigInfo);
2501 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2505 // copy string pointers from hash to array
2506 BEGIN_HASH_ITERATION(hash, itr)
2508 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2509 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2513 if (i > item_count) // should never happen
2516 END_HASH_ITERATION(hash, itr)
2518 // sort string pointers from hash in array
2519 qsort(sort_array, item_count, item_size, compareSetupFileData);
2521 if (!(file = fopen(filename, MODE_WRITE)))
2523 Warn("cannot write configuration file '%s'", filename);
2528 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2529 program.version_string));
2530 for (i = 0; i < item_count; i++)
2531 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2532 sort_array[i].value));
2535 checked_free(sort_array);
2538 SetupFileList *loadSetupFileList(char *filename)
2540 SetupFileList *setup_file_list = newSetupFileList("", "");
2541 SetupFileList *first_valid_list_entry;
2543 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2545 freeSetupFileList(setup_file_list);
2550 first_valid_list_entry = setup_file_list->next;
2552 // free empty list header
2553 setup_file_list->next = NULL;
2554 freeSetupFileList(setup_file_list);
2556 return first_valid_list_entry;
2559 SetupFileHash *loadSetupFileHash(char *filename)
2561 SetupFileHash *setup_file_hash = newSetupFileHash();
2563 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2565 freeSetupFileHash(setup_file_hash);
2570 return setup_file_hash;
2574 // ============================================================================
2576 // ============================================================================
2578 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2579 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2580 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2581 #define TOKEN_STR_LAST_USER "last_user"
2583 // level directory info
2584 #define LEVELINFO_TOKEN_IDENTIFIER 0
2585 #define LEVELINFO_TOKEN_NAME 1
2586 #define LEVELINFO_TOKEN_NAME_SORTING 2
2587 #define LEVELINFO_TOKEN_AUTHOR 3
2588 #define LEVELINFO_TOKEN_YEAR 4
2589 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2590 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2591 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2592 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2593 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2594 #define LEVELINFO_TOKEN_TESTED_BY 10
2595 #define LEVELINFO_TOKEN_LEVELS 11
2596 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2597 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2598 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2599 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2600 #define LEVELINFO_TOKEN_READONLY 16
2601 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2602 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2603 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2604 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2605 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2606 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2607 #define LEVELINFO_TOKEN_MUSIC_SET 23
2608 #define LEVELINFO_TOKEN_FILENAME 24
2609 #define LEVELINFO_TOKEN_FILETYPE 25
2610 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2611 #define LEVELINFO_TOKEN_HANDICAP 27
2612 #define LEVELINFO_TOKEN_SKIP_LEVELS 28
2613 #define LEVELINFO_TOKEN_USE_EMC_TILES 29
2615 #define NUM_LEVELINFO_TOKENS 30
2617 static LevelDirTree ldi;
2619 static struct TokenInfo levelinfo_tokens[] =
2621 // level directory info
2622 { TYPE_STRING, &ldi.identifier, "identifier" },
2623 { TYPE_STRING, &ldi.name, "name" },
2624 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2625 { TYPE_STRING, &ldi.author, "author" },
2626 { TYPE_STRING, &ldi.year, "year" },
2627 { TYPE_STRING, &ldi.program_title, "program_title" },
2628 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2629 { TYPE_STRING, &ldi.program_company, "program_company" },
2630 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2631 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2632 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2633 { TYPE_INTEGER, &ldi.levels, "levels" },
2634 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2635 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2636 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2637 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2638 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2639 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2640 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2641 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2642 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2643 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2644 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2645 { TYPE_STRING, &ldi.music_set, "music_set" },
2646 { TYPE_STRING, &ldi.level_filename, "filename" },
2647 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2648 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2649 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2650 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2651 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2654 static struct TokenInfo artworkinfo_tokens[] =
2656 // artwork directory info
2657 { TYPE_STRING, &ldi.identifier, "identifier" },
2658 { TYPE_STRING, &ldi.subdir, "subdir" },
2659 { TYPE_STRING, &ldi.name, "name" },
2660 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2661 { TYPE_STRING, &ldi.author, "author" },
2662 { TYPE_STRING, &ldi.program_title, "program_title" },
2663 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2664 { TYPE_STRING, &ldi.program_company, "program_company" },
2665 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2666 { TYPE_STRING, &ldi.basepath, "basepath" },
2667 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2668 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2669 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2674 static char *optional_tokens[] =
2677 "program_copyright",
2683 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2687 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2688 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2689 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2690 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2693 ti->node_parent = NULL;
2694 ti->node_group = NULL;
2701 ti->fullpath = NULL;
2702 ti->basepath = NULL;
2703 ti->identifier = NULL;
2704 ti->name = getStringCopy(ANONYMOUS_NAME);
2705 ti->name_sorting = NULL;
2706 ti->author = getStringCopy(ANONYMOUS_NAME);
2709 ti->program_title = NULL;
2710 ti->program_copyright = NULL;
2711 ti->program_company = NULL;
2713 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2714 ti->latest_engine = FALSE; // default: get from level
2715 ti->parent_link = FALSE;
2716 ti->is_copy = FALSE;
2717 ti->in_user_dir = FALSE;
2718 ti->user_defined = FALSE;
2720 ti->class_desc = NULL;
2722 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2724 if (ti->type == TREE_TYPE_LEVEL_DIR)
2726 ti->imported_from = NULL;
2727 ti->imported_by = NULL;
2728 ti->tested_by = NULL;
2730 ti->graphics_set_ecs = NULL;
2731 ti->graphics_set_aga = NULL;
2732 ti->graphics_set = NULL;
2733 ti->sounds_set_default = NULL;
2734 ti->sounds_set_lowpass = NULL;
2735 ti->sounds_set = NULL;
2736 ti->music_set = NULL;
2737 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2738 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2739 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2741 ti->level_filename = NULL;
2742 ti->level_filetype = NULL;
2744 ti->special_flags = NULL;
2747 ti->first_level = 0;
2749 ti->level_group = FALSE;
2750 ti->handicap_level = 0;
2751 ti->readonly = TRUE;
2752 ti->handicap = TRUE;
2753 ti->skip_levels = FALSE;
2755 ti->use_emc_tiles = FALSE;
2759 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2763 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2765 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2770 // copy all values from the parent structure
2772 ti->type = parent->type;
2774 ti->node_top = parent->node_top;
2775 ti->node_parent = parent;
2776 ti->node_group = NULL;
2783 ti->fullpath = NULL;
2784 ti->basepath = NULL;
2785 ti->identifier = NULL;
2786 ti->name = getStringCopy(ANONYMOUS_NAME);
2787 ti->name_sorting = NULL;
2788 ti->author = getStringCopy(parent->author);
2789 ti->year = getStringCopy(parent->year);
2791 ti->program_title = getStringCopy(parent->program_title);
2792 ti->program_copyright = getStringCopy(parent->program_copyright);
2793 ti->program_company = getStringCopy(parent->program_company);
2795 ti->sort_priority = parent->sort_priority;
2796 ti->latest_engine = parent->latest_engine;
2797 ti->parent_link = FALSE;
2798 ti->is_copy = FALSE;
2799 ti->in_user_dir = parent->in_user_dir;
2800 ti->user_defined = parent->user_defined;
2801 ti->color = parent->color;
2802 ti->class_desc = getStringCopy(parent->class_desc);
2804 ti->infotext = getStringCopy(parent->infotext);
2806 if (ti->type == TREE_TYPE_LEVEL_DIR)
2808 ti->imported_from = getStringCopy(parent->imported_from);
2809 ti->imported_by = getStringCopy(parent->imported_by);
2810 ti->tested_by = getStringCopy(parent->tested_by);
2812 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2813 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2814 ti->graphics_set = getStringCopy(parent->graphics_set);
2815 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2816 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2817 ti->sounds_set = getStringCopy(parent->sounds_set);
2818 ti->music_set = getStringCopy(parent->music_set);
2819 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2820 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2821 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2823 ti->level_filename = getStringCopy(parent->level_filename);
2824 ti->level_filetype = getStringCopy(parent->level_filetype);
2826 ti->special_flags = getStringCopy(parent->special_flags);
2828 ti->levels = parent->levels;
2829 ti->first_level = parent->first_level;
2830 ti->last_level = parent->last_level;
2831 ti->level_group = FALSE;
2832 ti->handicap_level = parent->handicap_level;
2833 ti->readonly = parent->readonly;
2834 ti->handicap = parent->handicap;
2835 ti->skip_levels = parent->skip_levels;
2837 ti->use_emc_tiles = parent->use_emc_tiles;
2841 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2843 TreeInfo *ti_copy = newTreeInfo();
2845 // copy all values from the original structure
2847 ti_copy->type = ti->type;
2849 ti_copy->node_top = ti->node_top;
2850 ti_copy->node_parent = ti->node_parent;
2851 ti_copy->node_group = ti->node_group;
2852 ti_copy->next = ti->next;
2854 ti_copy->cl_first = ti->cl_first;
2855 ti_copy->cl_cursor = ti->cl_cursor;
2857 ti_copy->subdir = getStringCopy(ti->subdir);
2858 ti_copy->fullpath = getStringCopy(ti->fullpath);
2859 ti_copy->basepath = getStringCopy(ti->basepath);
2860 ti_copy->identifier = getStringCopy(ti->identifier);
2861 ti_copy->name = getStringCopy(ti->name);
2862 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2863 ti_copy->author = getStringCopy(ti->author);
2864 ti_copy->year = getStringCopy(ti->year);
2866 ti_copy->program_title = getStringCopy(ti->program_title);
2867 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2868 ti_copy->program_company = getStringCopy(ti->program_company);
2870 ti_copy->imported_from = getStringCopy(ti->imported_from);
2871 ti_copy->imported_by = getStringCopy(ti->imported_by);
2872 ti_copy->tested_by = getStringCopy(ti->tested_by);
2874 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2875 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2876 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2877 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2878 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2879 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2880 ti_copy->music_set = getStringCopy(ti->music_set);
2881 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2882 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2883 ti_copy->music_path = getStringCopy(ti->music_path);
2885 ti_copy->level_filename = getStringCopy(ti->level_filename);
2886 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
2888 ti_copy->special_flags = getStringCopy(ti->special_flags);
2890 ti_copy->levels = ti->levels;
2891 ti_copy->first_level = ti->first_level;
2892 ti_copy->last_level = ti->last_level;
2893 ti_copy->sort_priority = ti->sort_priority;
2895 ti_copy->latest_engine = ti->latest_engine;
2897 ti_copy->level_group = ti->level_group;
2898 ti_copy->parent_link = ti->parent_link;
2899 ti_copy->is_copy = ti->is_copy;
2900 ti_copy->in_user_dir = ti->in_user_dir;
2901 ti_copy->user_defined = ti->user_defined;
2902 ti_copy->readonly = ti->readonly;
2903 ti_copy->handicap = ti->handicap;
2904 ti_copy->skip_levels = ti->skip_levels;
2906 ti_copy->use_emc_tiles = ti->use_emc_tiles;
2908 ti_copy->color = ti->color;
2909 ti_copy->class_desc = getStringCopy(ti->class_desc);
2910 ti_copy->handicap_level = ti->handicap_level;
2912 ti_copy->infotext = getStringCopy(ti->infotext);
2917 void freeTreeInfo(TreeInfo *ti)
2922 checked_free(ti->subdir);
2923 checked_free(ti->fullpath);
2924 checked_free(ti->basepath);
2925 checked_free(ti->identifier);
2927 checked_free(ti->name);
2928 checked_free(ti->name_sorting);
2929 checked_free(ti->author);
2930 checked_free(ti->year);
2932 checked_free(ti->program_title);
2933 checked_free(ti->program_copyright);
2934 checked_free(ti->program_company);
2936 checked_free(ti->class_desc);
2938 checked_free(ti->infotext);
2940 if (ti->type == TREE_TYPE_LEVEL_DIR)
2942 checked_free(ti->imported_from);
2943 checked_free(ti->imported_by);
2944 checked_free(ti->tested_by);
2946 checked_free(ti->graphics_set_ecs);
2947 checked_free(ti->graphics_set_aga);
2948 checked_free(ti->graphics_set);
2949 checked_free(ti->sounds_set_default);
2950 checked_free(ti->sounds_set_lowpass);
2951 checked_free(ti->sounds_set);
2952 checked_free(ti->music_set);
2954 checked_free(ti->graphics_path);
2955 checked_free(ti->sounds_path);
2956 checked_free(ti->music_path);
2958 checked_free(ti->level_filename);
2959 checked_free(ti->level_filetype);
2961 checked_free(ti->special_flags);
2964 // recursively free child node
2966 freeTreeInfo(ti->node_group);
2968 // recursively free next node
2970 freeTreeInfo(ti->next);
2975 void setSetupInfo(struct TokenInfo *token_info,
2976 int token_nr, char *token_value)
2978 int token_type = token_info[token_nr].type;
2979 void *setup_value = token_info[token_nr].value;
2981 if (token_value == NULL)
2984 // set setup field to corresponding token value
2989 *(boolean *)setup_value = get_boolean_from_string(token_value);
2993 *(int *)setup_value = get_switch3_from_string(token_value);
2997 *(Key *)setup_value = getKeyFromKeyName(token_value);
3001 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3005 *(int *)setup_value = get_integer_from_string(token_value);
3009 checked_free(*(char **)setup_value);
3010 *(char **)setup_value = getStringCopy(token_value);
3014 *(int *)setup_value = get_player_nr_from_string(token_value);
3022 static int compareTreeInfoEntries(const void *object1, const void *object2)
3024 const TreeInfo *entry1 = *((TreeInfo **)object1);
3025 const TreeInfo *entry2 = *((TreeInfo **)object2);
3026 int tree_sorting1 = TREE_SORTING(entry1);
3027 int tree_sorting2 = TREE_SORTING(entry2);
3029 if (tree_sorting1 != tree_sorting2)
3030 return (tree_sorting1 - tree_sorting2);
3032 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3035 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3039 if (node_parent == NULL)
3042 ti_new = newTreeInfo();
3043 setTreeInfoToDefaults(ti_new, node_parent->type);
3045 ti_new->node_parent = node_parent;
3046 ti_new->parent_link = TRUE;
3048 setString(&ti_new->identifier, node_parent->identifier);
3049 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3050 setString(&ti_new->name_sorting, ti_new->name);
3052 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3053 setString(&ti_new->fullpath, node_parent->fullpath);
3055 ti_new->sort_priority = LEVELCLASS_PARENT;
3056 ti_new->latest_engine = node_parent->latest_engine;
3058 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3060 pushTreeInfo(&node_parent->node_group, ti_new);
3065 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3067 if (node_first == NULL)
3070 TreeInfo *ti_new = newTreeInfo();
3071 int type = node_first->type;
3073 setTreeInfoToDefaults(ti_new, type);
3075 ti_new->node_parent = NULL;
3076 ti_new->parent_link = FALSE;
3078 setString(&ti_new->identifier, "top_tree_node");
3079 setString(&ti_new->name, TREE_INFOTEXT(type));
3080 setString(&ti_new->name_sorting, ti_new->name);
3082 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3083 setString(&ti_new->fullpath, ".");
3085 ti_new->sort_priority = LEVELCLASS_TOP;
3086 ti_new->latest_engine = node_first->latest_engine;
3088 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3090 ti_new->node_group = node_first;
3091 ti_new->level_group = TRUE;
3093 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3095 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3096 setString(&ti_new2->name_sorting, ti_new2->name);
3101 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3105 if (node->node_group)
3106 setTreeInfoParentNodes(node->node_group, node);
3108 node->node_parent = node_parent;
3114 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3116 // add top tree node with back link node in previous tree
3117 node_first = createTopTreeInfoNode(node_first);
3119 // set all parent links (back links) in complete tree
3120 setTreeInfoParentNodes(node_first, NULL);
3126 // ----------------------------------------------------------------------------
3127 // functions for handling level and custom artwork info cache
3128 // ----------------------------------------------------------------------------
3130 static void LoadArtworkInfoCache(void)
3132 InitCacheDirectory();
3134 if (artworkinfo_cache_old == NULL)
3136 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3138 // try to load artwork info hash from already existing cache file
3139 artworkinfo_cache_old = loadSetupFileHash(filename);
3141 // try to get program version that artwork info cache was written with
3142 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3144 // check program version of artwork info cache against current version
3145 if (!strEqual(version, program.version_string))
3147 freeSetupFileHash(artworkinfo_cache_old);
3149 artworkinfo_cache_old = NULL;
3152 // if no artwork info cache file was found, start with empty hash
3153 if (artworkinfo_cache_old == NULL)
3154 artworkinfo_cache_old = newSetupFileHash();
3159 if (artworkinfo_cache_new == NULL)
3160 artworkinfo_cache_new = newSetupFileHash();
3162 update_artworkinfo_cache = FALSE;
3165 static void SaveArtworkInfoCache(void)
3167 if (!update_artworkinfo_cache)
3170 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3172 InitCacheDirectory();
3174 saveSetupFileHash(artworkinfo_cache_new, filename);
3179 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3181 static char *prefix = NULL;
3183 checked_free(prefix);
3185 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3190 // (identical to above function, but separate string buffer needed -- nasty)
3191 static char *getCacheToken(char *prefix, char *suffix)
3193 static char *token = NULL;
3195 checked_free(token);
3197 token = getStringCat2WithSeparator(prefix, suffix, ".");
3202 static char *getFileTimestampString(char *filename)
3204 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3207 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3209 struct stat file_status;
3211 if (timestamp_string == NULL)
3214 if (!fileExists(filename)) // file does not exist
3215 return (atoi(timestamp_string) != 0);
3217 if (stat(filename, &file_status) != 0) // cannot stat file
3220 return (file_status.st_mtime != atoi(timestamp_string));
3223 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3225 char *identifier = level_node->subdir;
3226 char *type_string = ARTWORK_DIRECTORY(type);
3227 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3228 char *token_main = getCacheToken(token_prefix, "CACHED");
3229 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3230 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3231 TreeInfo *artwork_info = NULL;
3233 if (!use_artworkinfo_cache)
3236 if (optional_tokens_hash == NULL)
3240 // create hash from list of optional tokens (for quick access)
3241 optional_tokens_hash = newSetupFileHash();
3242 for (i = 0; optional_tokens[i] != NULL; i++)
3243 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3250 artwork_info = newTreeInfo();
3251 setTreeInfoToDefaults(artwork_info, type);
3253 // set all structure fields according to the token/value pairs
3254 ldi = *artwork_info;
3255 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3257 char *token_suffix = artworkinfo_tokens[i].text;
3258 char *token = getCacheToken(token_prefix, token_suffix);
3259 char *value = getHashEntry(artworkinfo_cache_old, token);
3261 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3263 setSetupInfo(artworkinfo_tokens, i, value);
3265 // check if cache entry for this item is mandatory, but missing
3266 if (value == NULL && !optional)
3268 Warn("missing cache entry '%s'", token);
3274 *artwork_info = ldi;
3279 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3280 LEVELINFO_FILENAME);
3281 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3282 ARTWORKINFO_FILENAME(type));
3284 // check if corresponding "levelinfo.conf" file has changed
3285 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3286 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3288 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3291 // check if corresponding "<artworkinfo>.conf" file has changed
3292 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3293 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3295 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3298 checked_free(filename_levelinfo);
3299 checked_free(filename_artworkinfo);
3302 if (!cached && artwork_info != NULL)
3304 freeTreeInfo(artwork_info);
3309 return artwork_info;
3312 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3313 LevelDirTree *level_node, int type)
3315 char *identifier = level_node->subdir;
3316 char *type_string = ARTWORK_DIRECTORY(type);
3317 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3318 char *token_main = getCacheToken(token_prefix, "CACHED");
3319 boolean set_cache_timestamps = TRUE;
3322 setHashEntry(artworkinfo_cache_new, token_main, "true");
3324 if (set_cache_timestamps)
3326 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3327 LEVELINFO_FILENAME);
3328 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3329 ARTWORKINFO_FILENAME(type));
3330 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3331 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3333 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3334 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3336 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3337 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3339 checked_free(filename_levelinfo);
3340 checked_free(filename_artworkinfo);
3341 checked_free(timestamp_levelinfo);
3342 checked_free(timestamp_artworkinfo);
3345 ldi = *artwork_info;
3346 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3348 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3349 char *value = getSetupValue(artworkinfo_tokens[i].type,
3350 artworkinfo_tokens[i].value);
3352 setHashEntry(artworkinfo_cache_new, token, value);
3357 // ----------------------------------------------------------------------------
3358 // functions for loading level info and custom artwork info
3359 // ----------------------------------------------------------------------------
3361 int GetZipFileTreeType(char *zip_filename)
3363 static char *top_dir_path = NULL;
3364 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3365 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3367 GRAPHICSINFO_FILENAME,
3368 SOUNDSINFO_FILENAME,
3374 checked_free(top_dir_path);
3375 top_dir_path = NULL;
3377 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3379 checked_free(top_dir_conf_filename[j]);
3380 top_dir_conf_filename[j] = NULL;
3383 char **zip_entries = zip_list(zip_filename);
3385 // check if zip file successfully opened
3386 if (zip_entries == NULL || zip_entries[0] == NULL)
3387 return TREE_TYPE_UNDEFINED;
3389 // first zip file entry is expected to be top level directory
3390 char *top_dir = zip_entries[0];
3392 // check if valid top level directory found in zip file
3393 if (!strSuffix(top_dir, "/"))
3394 return TREE_TYPE_UNDEFINED;
3396 // get filenames of valid configuration files in top level directory
3397 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3398 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3400 int tree_type = TREE_TYPE_UNDEFINED;
3403 while (zip_entries[e] != NULL)
3405 // check if every zip file entry is below top level directory
3406 if (!strPrefix(zip_entries[e], top_dir))
3407 return TREE_TYPE_UNDEFINED;
3409 // check if this zip file entry is a valid configuration filename
3410 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3412 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3414 // only exactly one valid configuration file allowed
3415 if (tree_type != TREE_TYPE_UNDEFINED)
3416 return TREE_TYPE_UNDEFINED;
3428 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3431 static char *top_dir_path = NULL;
3432 static char *top_dir_conf_filename = NULL;
3434 checked_free(top_dir_path);
3435 checked_free(top_dir_conf_filename);
3437 top_dir_path = NULL;
3438 top_dir_conf_filename = NULL;
3440 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3441 ARTWORKINFO_FILENAME(tree_type));
3443 // check if valid configuration filename determined
3444 if (conf_basename == NULL || strEqual(conf_basename, ""))
3447 char **zip_entries = zip_list(zip_filename);
3449 // check if zip file successfully opened
3450 if (zip_entries == NULL || zip_entries[0] == NULL)
3453 // first zip file entry is expected to be top level directory
3454 char *top_dir = zip_entries[0];
3456 // check if valid top level directory found in zip file
3457 if (!strSuffix(top_dir, "/"))
3460 // get path of extracted top level directory
3461 top_dir_path = getPath2(directory, top_dir);
3463 // remove trailing directory separator from top level directory path
3464 // (required to be able to check for file and directory in next step)
3465 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3467 // check if zip file's top level directory already exists in target directory
3468 if (fileExists(top_dir_path)) // (checks for file and directory)
3471 // get filename of configuration file in top level directory
3472 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3474 boolean found_top_dir_conf_filename = FALSE;
3477 while (zip_entries[i] != NULL)
3479 // check if every zip file entry is below top level directory
3480 if (!strPrefix(zip_entries[i], top_dir))
3483 // check if this zip file entry is the configuration filename
3484 if (strEqual(zip_entries[i], top_dir_conf_filename))
3485 found_top_dir_conf_filename = TRUE;
3490 // check if valid configuration filename was found in zip file
3491 if (!found_top_dir_conf_filename)
3497 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3500 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3503 if (!zip_file_valid)
3505 Warn("zip file '%s' rejected!", zip_filename);
3510 char **zip_entries = zip_extract(zip_filename, directory);
3512 if (zip_entries == NULL)
3514 Warn("zip file '%s' could not be extracted!", zip_filename);
3519 Info("zip file '%s' successfully extracted!", zip_filename);
3521 // first zip file entry contains top level directory
3522 char *top_dir = zip_entries[0];
3524 // remove trailing directory separator from top level directory
3525 top_dir[strlen(top_dir) - 1] = '\0';
3530 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3533 DirectoryEntry *dir_entry;
3535 if ((dir = openDirectory(directory)) == NULL)
3537 // display error if directory is main "options.graphics_directory" etc.
3538 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3539 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3540 Warn("cannot read directory '%s'", directory);
3545 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3547 // skip non-zip files (and also directories with zip extension)
3548 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3551 char *zip_filename = getPath2(directory, dir_entry->basename);
3552 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3553 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3555 // check if zip file hasn't already been extracted or rejected
3556 if (!fileExists(zip_filename_extracted) &&
3557 !fileExists(zip_filename_rejected))
3559 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3561 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3562 zip_filename_rejected);
3565 // create empty file to mark zip file as extracted or rejected
3566 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3567 fclose(marker_file);
3570 free(zip_filename_extracted);
3571 free(zip_filename_rejected);
3575 closeDirectory(dir);
3578 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3579 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3581 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3582 TreeInfo *node_parent,
3583 char *level_directory,
3584 char *directory_name)
3586 char *directory_path = getPath2(level_directory, directory_name);
3587 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3588 SetupFileHash *setup_file_hash;
3589 LevelDirTree *leveldir_new = NULL;
3592 // unless debugging, silently ignore directories without "levelinfo.conf"
3593 if (!options.debug && !fileExists(filename))
3595 free(directory_path);
3601 setup_file_hash = loadSetupFileHash(filename);
3603 if (setup_file_hash == NULL)
3605 #if DEBUG_NO_CONFIG_FILE
3606 Debug("setup", "ignoring level directory '%s'", directory_path);
3609 free(directory_path);
3615 leveldir_new = newTreeInfo();
3618 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3620 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3622 leveldir_new->subdir = getStringCopy(directory_name);
3624 // set all structure fields according to the token/value pairs
3625 ldi = *leveldir_new;
3626 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3627 setSetupInfo(levelinfo_tokens, i,
3628 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3629 *leveldir_new = ldi;
3631 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3632 setString(&leveldir_new->name, leveldir_new->subdir);
3634 if (leveldir_new->identifier == NULL)
3635 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3637 if (leveldir_new->name_sorting == NULL)
3638 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3640 if (node_parent == NULL) // top level group
3642 leveldir_new->basepath = getStringCopy(level_directory);
3643 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3645 else // sub level group
3647 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3648 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3651 leveldir_new->last_level =
3652 leveldir_new->first_level + leveldir_new->levels - 1;
3654 leveldir_new->in_user_dir =
3655 (!strEqual(leveldir_new->basepath, options.level_directory));
3657 // adjust some settings if user's private level directory was detected
3658 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3659 leveldir_new->in_user_dir &&
3660 (strEqual(leveldir_new->subdir, getLoginName()) ||
3661 strEqual(leveldir_new->name, getLoginName()) ||
3662 strEqual(leveldir_new->author, getRealName())))
3664 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3665 leveldir_new->readonly = FALSE;
3668 leveldir_new->user_defined =
3669 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3671 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3673 leveldir_new->handicap_level = // set handicap to default value
3674 (leveldir_new->user_defined || !leveldir_new->handicap ?
3675 leveldir_new->last_level : leveldir_new->first_level);
3677 DrawInitTextItem(leveldir_new->name);
3679 pushTreeInfo(node_first, leveldir_new);
3681 freeSetupFileHash(setup_file_hash);
3683 if (leveldir_new->level_group)
3685 // create node to link back to current level directory
3686 createParentTreeInfoNode(leveldir_new);
3688 // recursively step into sub-directory and look for more level series
3689 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3690 leveldir_new, directory_path);
3693 free(directory_path);
3699 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3700 TreeInfo *node_parent,
3701 char *level_directory)
3703 // ---------- 1st stage: process any level set zip files ----------
3705 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3707 // ---------- 2nd stage: check for level set directories ----------
3710 DirectoryEntry *dir_entry;
3711 boolean valid_entry_found = FALSE;
3713 if ((dir = openDirectory(level_directory)) == NULL)
3715 Warn("cannot read level directory '%s'", level_directory);
3720 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3722 char *directory_name = dir_entry->basename;
3723 char *directory_path = getPath2(level_directory, directory_name);
3725 // skip entries for current and parent directory
3726 if (strEqual(directory_name, ".") ||
3727 strEqual(directory_name, ".."))
3729 free(directory_path);
3734 // find out if directory entry is itself a directory
3735 if (!dir_entry->is_directory) // not a directory
3737 free(directory_path);
3742 free(directory_path);
3744 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3745 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3746 strEqual(directory_name, MUSIC_DIRECTORY))
3749 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3754 closeDirectory(dir);
3756 // special case: top level directory may directly contain "levelinfo.conf"
3757 if (node_parent == NULL && !valid_entry_found)
3759 // check if this directory directly contains a file "levelinfo.conf"
3760 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3761 level_directory, ".");
3764 if (!valid_entry_found)
3765 Warn("cannot find any valid level series in directory '%s'",
3769 boolean AdjustGraphicsForEMC(void)
3771 boolean settings_changed = FALSE;
3773 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3774 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3776 return settings_changed;
3779 boolean AdjustSoundsForEMC(void)
3781 boolean settings_changed = FALSE;
3783 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3784 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3786 return settings_changed;
3789 void LoadLevelInfo(void)
3791 InitUserLevelDirectory(getLoginName());
3793 DrawInitTextHead("Loading level series");
3795 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3796 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3798 leveldir_first = createTopTreeInfoNode(leveldir_first);
3800 /* after loading all level set information, clone the level directory tree
3801 and remove all level sets without levels (these may still contain artwork
3802 to be offered in the setup menu as "custom artwork", and are therefore
3803 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3804 leveldir_first_all = leveldir_first;
3805 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3807 AdjustGraphicsForEMC();
3808 AdjustSoundsForEMC();
3810 // before sorting, the first entries will be from the user directory
3811 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3813 if (leveldir_first == NULL)
3814 Fail("cannot find any valid level series in any directory");
3816 sortTreeInfo(&leveldir_first);
3818 #if ENABLE_UNUSED_CODE
3819 dumpTreeInfo(leveldir_first, 0);
3823 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3824 TreeInfo *node_parent,
3825 char *base_directory,
3826 char *directory_name, int type)
3828 char *directory_path = getPath2(base_directory, directory_name);
3829 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3830 SetupFileHash *setup_file_hash = NULL;
3831 TreeInfo *artwork_new = NULL;
3834 if (fileExists(filename))
3835 setup_file_hash = loadSetupFileHash(filename);
3837 if (setup_file_hash == NULL) // no config file -- look for artwork files
3840 DirectoryEntry *dir_entry;
3841 boolean valid_file_found = FALSE;
3843 if ((dir = openDirectory(directory_path)) != NULL)
3845 while ((dir_entry = readDirectory(dir)) != NULL)
3847 if (FileIsArtworkType(dir_entry->filename, type))
3849 valid_file_found = TRUE;
3855 closeDirectory(dir);
3858 if (!valid_file_found)
3860 #if DEBUG_NO_CONFIG_FILE
3861 if (!strEqual(directory_name, "."))
3862 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3865 free(directory_path);
3872 artwork_new = newTreeInfo();
3875 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
3877 setTreeInfoToDefaults(artwork_new, type);
3879 artwork_new->subdir = getStringCopy(directory_name);
3881 if (setup_file_hash) // (before defining ".color" and ".class_desc")
3883 // set all structure fields according to the token/value pairs
3885 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3886 setSetupInfo(levelinfo_tokens, i,
3887 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3890 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
3891 setString(&artwork_new->name, artwork_new->subdir);
3893 if (artwork_new->identifier == NULL)
3894 artwork_new->identifier = getStringCopy(artwork_new->subdir);
3896 if (artwork_new->name_sorting == NULL)
3897 artwork_new->name_sorting = getStringCopy(artwork_new->name);
3900 if (node_parent == NULL) // top level group
3902 artwork_new->basepath = getStringCopy(base_directory);
3903 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
3905 else // sub level group
3907 artwork_new->basepath = getStringCopy(node_parent->basepath);
3908 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3911 artwork_new->in_user_dir =
3912 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
3914 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
3916 if (setup_file_hash == NULL) // (after determining ".user_defined")
3918 if (strEqual(artwork_new->subdir, "."))
3920 if (artwork_new->user_defined)
3922 setString(&artwork_new->identifier, "private");
3923 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
3927 setString(&artwork_new->identifier, "classic");
3928 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
3931 setString(&artwork_new->class_desc,
3932 getLevelClassDescription(artwork_new));
3936 setString(&artwork_new->identifier, artwork_new->subdir);
3939 setString(&artwork_new->name, artwork_new->identifier);
3940 setString(&artwork_new->name_sorting, artwork_new->name);
3943 pushTreeInfo(node_first, artwork_new);
3945 freeSetupFileHash(setup_file_hash);
3947 free(directory_path);
3953 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
3954 TreeInfo *node_parent,
3955 char *base_directory, int type)
3957 // ---------- 1st stage: process any artwork set zip files ----------
3959 ProcessZipFilesInDirectory(base_directory, type);
3961 // ---------- 2nd stage: check for artwork set directories ----------
3964 DirectoryEntry *dir_entry;
3965 boolean valid_entry_found = FALSE;
3967 if ((dir = openDirectory(base_directory)) == NULL)
3969 // display error if directory is main "options.graphics_directory" etc.
3970 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
3971 Warn("cannot read directory '%s'", base_directory);
3976 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3978 char *directory_name = dir_entry->basename;
3979 char *directory_path = getPath2(base_directory, directory_name);
3981 // skip directory entries for current and parent directory
3982 if (strEqual(directory_name, ".") ||
3983 strEqual(directory_name, ".."))
3985 free(directory_path);
3990 // skip directory entries which are not a directory
3991 if (!dir_entry->is_directory) // not a directory
3993 free(directory_path);
3998 free(directory_path);
4000 // check if this directory contains artwork with or without config file
4001 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4003 directory_name, type);
4006 closeDirectory(dir);
4008 // check if this directory directly contains artwork itself
4009 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4010 base_directory, ".",
4012 if (!valid_entry_found)
4013 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4016 static TreeInfo *getDummyArtworkInfo(int type)
4018 // this is only needed when there is completely no artwork available
4019 TreeInfo *artwork_new = newTreeInfo();
4021 setTreeInfoToDefaults(artwork_new, type);
4023 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4024 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4025 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4027 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4028 setString(&artwork_new->name, UNDEFINED_FILENAME);
4029 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4034 void SetCurrentArtwork(int type)
4036 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4037 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4038 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4039 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4041 // set current artwork to artwork configured in setup menu
4042 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4044 // if not found, set current artwork to default artwork
4045 if (*current_ptr == NULL)
4046 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4048 // if not found, set current artwork to first artwork in tree
4049 if (*current_ptr == NULL)
4050 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4053 void ChangeCurrentArtworkIfNeeded(int type)
4055 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4056 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4058 if (!strEqual(current_identifier, setup_set))
4059 SetCurrentArtwork(type);
4062 void LoadArtworkInfo(void)
4064 LoadArtworkInfoCache();
4066 DrawInitTextHead("Looking for custom artwork");
4068 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4069 options.graphics_directory,
4070 TREE_TYPE_GRAPHICS_DIR);
4071 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4072 getUserGraphicsDir(),
4073 TREE_TYPE_GRAPHICS_DIR);
4075 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4076 options.sounds_directory,
4077 TREE_TYPE_SOUNDS_DIR);
4078 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4080 TREE_TYPE_SOUNDS_DIR);
4082 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4083 options.music_directory,
4084 TREE_TYPE_MUSIC_DIR);
4085 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4087 TREE_TYPE_MUSIC_DIR);
4089 if (artwork.gfx_first == NULL)
4090 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4091 if (artwork.snd_first == NULL)
4092 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4093 if (artwork.mus_first == NULL)
4094 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4096 // before sorting, the first entries will be from the user directory
4097 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4098 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4099 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4101 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4102 artwork.snd_current_identifier = artwork.snd_current->identifier;
4103 artwork.mus_current_identifier = artwork.mus_current->identifier;
4105 #if ENABLE_UNUSED_CODE
4106 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4107 artwork.gfx_current_identifier);
4108 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4109 artwork.snd_current_identifier);
4110 Debug("setup:LoadArtworkInfo", "music set == %s",
4111 artwork.mus_current_identifier);
4114 sortTreeInfo(&artwork.gfx_first);
4115 sortTreeInfo(&artwork.snd_first);
4116 sortTreeInfo(&artwork.mus_first);
4118 #if ENABLE_UNUSED_CODE
4119 dumpTreeInfo(artwork.gfx_first, 0);
4120 dumpTreeInfo(artwork.snd_first, 0);
4121 dumpTreeInfo(artwork.mus_first, 0);
4125 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4127 ArtworkDirTree *artwork_new = newTreeInfo();
4128 char *top_node_name = "standalone artwork";
4130 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4132 artwork_new->level_group = TRUE;
4134 setString(&artwork_new->identifier, top_node_name);
4135 setString(&artwork_new->name, top_node_name);
4136 setString(&artwork_new->name_sorting, top_node_name);
4138 // create node to link back to current custom artwork directory
4139 createParentTreeInfoNode(artwork_new);
4141 // move existing custom artwork tree into newly created sub-tree
4142 artwork_new->node_group->next = *artwork_node;
4144 // change custom artwork tree to contain only newly created node
4145 *artwork_node = artwork_new;
4148 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4149 ArtworkDirTree *node_parent,
4150 LevelDirTree *level_node,
4151 boolean empty_level_set_mode)
4153 int type = (*artwork_node)->type;
4155 // recursively check all level directories for artwork sub-directories
4159 boolean empty_level_set = (level_node->levels == 0);
4161 // check all tree entries for artwork, but skip parent link entries
4162 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4164 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4165 boolean cached = (artwork_new != NULL);
4169 pushTreeInfo(artwork_node, artwork_new);
4173 TreeInfo *topnode_last = *artwork_node;
4174 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4175 ARTWORK_DIRECTORY(type));
4177 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4179 if (topnode_last != *artwork_node) // check for newly added node
4181 artwork_new = *artwork_node;
4183 setString(&artwork_new->identifier, level_node->subdir);
4184 setString(&artwork_new->name, level_node->name);
4185 setString(&artwork_new->name_sorting, level_node->name_sorting);
4187 artwork_new->sort_priority = level_node->sort_priority;
4188 artwork_new->in_user_dir = level_node->in_user_dir;
4190 update_artworkinfo_cache = TRUE;
4196 // insert artwork info (from old cache or filesystem) into new cache
4197 if (artwork_new != NULL)
4198 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4201 DrawInitTextItem(level_node->name);
4203 if (level_node->node_group != NULL)
4205 TreeInfo *artwork_new = newTreeInfo();
4208 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4210 setTreeInfoToDefaults(artwork_new, type);
4212 artwork_new->level_group = TRUE;
4214 setString(&artwork_new->identifier, level_node->subdir);
4216 if (node_parent == NULL) // check for top tree node
4218 char *top_node_name = (empty_level_set_mode ?
4219 "artwork for certain level sets" :
4220 "artwork included in level sets");
4222 setString(&artwork_new->name, top_node_name);
4223 setString(&artwork_new->name_sorting, top_node_name);
4227 setString(&artwork_new->name, level_node->name);
4228 setString(&artwork_new->name_sorting, level_node->name_sorting);
4231 pushTreeInfo(artwork_node, artwork_new);
4233 // create node to link back to current custom artwork directory
4234 createParentTreeInfoNode(artwork_new);
4236 // recursively step into sub-directory and look for more custom artwork
4237 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4238 level_node->node_group,
4239 empty_level_set_mode);
4241 // if sub-tree has no custom artwork at all, remove it
4242 if (artwork_new->node_group->next == NULL)
4243 removeTreeInfo(artwork_node);
4246 level_node = level_node->next;
4250 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4252 // move peviously loaded artwork tree into separate sub-tree
4253 MoveArtworkInfoIntoSubTree(artwork_node);
4255 // load artwork from level sets into separate sub-trees
4256 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4257 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4259 // add top tree node over all sub-trees and set parent links
4260 *artwork_node = addTopTreeInfoNode(*artwork_node);
4263 void LoadLevelArtworkInfo(void)
4265 print_timestamp_init("LoadLevelArtworkInfo");
4267 DrawInitTextHead("Looking for custom level artwork");
4269 print_timestamp_time("DrawTimeText");
4271 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4272 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4273 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4274 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4275 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4276 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4278 SaveArtworkInfoCache();
4280 print_timestamp_time("SaveArtworkInfoCache");
4282 // needed for reloading level artwork not known at ealier stage
4283 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4284 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4285 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4287 print_timestamp_time("getTreeInfoFromIdentifier");
4289 sortTreeInfo(&artwork.gfx_first);
4290 sortTreeInfo(&artwork.snd_first);
4291 sortTreeInfo(&artwork.mus_first);
4293 print_timestamp_time("sortTreeInfo");
4295 #if ENABLE_UNUSED_CODE
4296 dumpTreeInfo(artwork.gfx_first, 0);
4297 dumpTreeInfo(artwork.snd_first, 0);
4298 dumpTreeInfo(artwork.mus_first, 0);
4301 print_timestamp_done("LoadLevelArtworkInfo");
4304 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4305 char *tree_subdir_new, int type)
4307 if (tree_node_old == NULL)
4309 if (type == TREE_TYPE_LEVEL_DIR)
4311 // get level info tree node of personal user level set
4312 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4314 // this may happen if "setup.internal.create_user_levelset" is FALSE
4315 // or if file "levelinfo.conf" is missing in personal user level set
4316 if (tree_node_old == NULL)
4317 tree_node_old = leveldir_first->node_group;
4321 // get artwork info tree node of first artwork set
4322 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4326 if (tree_dir == NULL)
4327 tree_dir = TREE_USERDIR(type);
4329 if (tree_node_old == NULL ||
4331 tree_subdir_new == NULL) // should not happen
4334 int draw_deactivation_mask = GetDrawDeactivationMask();
4336 // override draw deactivation mask (temporarily disable drawing)
4337 SetDrawDeactivationMask(REDRAW_ALL);
4339 if (type == TREE_TYPE_LEVEL_DIR)
4341 // load new level set config and add it next to first user level set
4342 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4343 tree_node_old->node_parent,
4344 tree_dir, tree_subdir_new);
4348 // load new artwork set config and add it next to first artwork set
4349 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4350 tree_node_old->node_parent,
4351 tree_dir, tree_subdir_new, type);
4354 // set draw deactivation mask to previous value
4355 SetDrawDeactivationMask(draw_deactivation_mask);
4357 // get first node of level or artwork info tree
4358 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4360 // get tree info node of newly added level or artwork set
4361 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4364 if (tree_node_new == NULL) // should not happen
4367 // correct top link and parent node link of newly created tree node
4368 tree_node_new->node_top = tree_node_old->node_top;
4369 tree_node_new->node_parent = tree_node_old->node_parent;
4371 // sort tree info to adjust position of newly added tree set
4372 sortTreeInfo(tree_node_first);
4377 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4378 char *tree_subdir_new, int type)
4380 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4381 Fail("internal tree info structure corrupted -- aborting");
4384 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4386 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4389 char *getArtworkIdentifierForUserLevelSet(int type)
4391 char *classic_artwork_set = getClassicArtworkSet(type);
4393 // check for custom artwork configured in "levelinfo.conf"
4394 char *leveldir_artwork_set =
4395 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4396 boolean has_leveldir_artwork_set =
4397 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4398 classic_artwork_set));
4400 // check for custom artwork in sub-directory "graphics" etc.
4401 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4402 char *leveldir_identifier = leveldir_current->identifier;
4403 boolean has_artwork_subdir =
4404 (getTreeInfoFromIdentifier(artwork_first_node,
4405 leveldir_identifier) != NULL);
4407 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4408 has_artwork_subdir ? leveldir_identifier :
4409 classic_artwork_set);
4412 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4414 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4415 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4416 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4420 ti = getTreeInfoFromIdentifier(artwork_first_node,
4421 ARTWORK_DEFAULT_SUBDIR(type));
4423 Fail("cannot find default graphics -- should not happen");
4429 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4431 char *graphics_set =
4432 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4434 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4436 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4438 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4439 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4440 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4443 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4444 char *level_author, int num_levels)
4446 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4447 char *filename_tmp = getStringCat2(filename, ".tmp");
4449 FILE *file_tmp = NULL;
4450 char line[MAX_LINE_LEN];
4451 boolean success = FALSE;
4452 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4454 // update values in level directory tree
4456 if (level_name != NULL)
4457 setString(&leveldir->name, level_name);
4459 if (level_author != NULL)
4460 setString(&leveldir->author, level_author);
4462 if (num_levels != -1)
4463 leveldir->levels = num_levels;
4465 // update values that depend on other values
4467 setString(&leveldir->name_sorting, leveldir->name);
4469 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4471 // sort order of level sets may have changed
4472 sortTreeInfo(&leveldir_first);
4474 if ((file = fopen(filename, MODE_READ)) &&
4475 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4477 while (fgets(line, MAX_LINE_LEN, file))
4479 if (strPrefix(line, "name:") && level_name != NULL)
4480 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4481 else if (strPrefix(line, "author:") && level_author != NULL)
4482 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4483 else if (strPrefix(line, "levels:") && num_levels != -1)
4484 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4486 fputs(line, file_tmp);
4499 success = (rename(filename_tmp, filename) == 0);
4507 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4508 char *level_author, int num_levels,
4509 boolean use_artwork_set)
4511 LevelDirTree *level_info;
4516 // create user level sub-directory, if needed
4517 createDirectory(getUserLevelDir(level_subdir), "user level", PERMS_PRIVATE);
4519 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4521 if (!(file = fopen(filename, MODE_WRITE)))
4523 Warn("cannot write level info file '%s'", filename);
4530 level_info = newTreeInfo();
4532 // always start with reliable default values
4533 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4535 setString(&level_info->name, level_name);
4536 setString(&level_info->author, level_author);
4537 level_info->levels = num_levels;
4538 level_info->first_level = 1;
4539 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4540 level_info->readonly = FALSE;
4542 if (use_artwork_set)
4544 level_info->graphics_set =
4545 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4546 level_info->sounds_set =
4547 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4548 level_info->music_set =
4549 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4552 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4554 fprintFileHeader(file, LEVELINFO_FILENAME);
4557 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4559 if (i == LEVELINFO_TOKEN_NAME ||
4560 i == LEVELINFO_TOKEN_AUTHOR ||
4561 i == LEVELINFO_TOKEN_LEVELS ||
4562 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4563 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4564 i == LEVELINFO_TOKEN_READONLY ||
4565 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4566 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4567 i == LEVELINFO_TOKEN_MUSIC_SET)))
4568 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4570 // just to make things nicer :)
4571 if (i == LEVELINFO_TOKEN_AUTHOR ||
4572 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4573 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4574 fprintf(file, "\n");
4577 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4581 SetFilePermissions(filename, PERMS_PRIVATE);
4583 freeTreeInfo(level_info);
4589 static void SaveUserLevelInfo(void)
4591 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4594 char *getSetupValue(int type, void *value)
4596 static char value_string[MAX_LINE_LEN];
4604 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4608 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4612 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4613 *(int *)value == FALSE ? "off" : "on"));
4617 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4620 case TYPE_YES_NO_AUTO:
4621 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4622 *(int *)value == FALSE ? "no" : "yes"));
4626 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4630 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4634 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4638 sprintf(value_string, "%d", *(int *)value);
4642 if (*(char **)value == NULL)
4645 strcpy(value_string, *(char **)value);
4649 sprintf(value_string, "player_%d", *(int *)value + 1);
4653 value_string[0] = '\0';
4657 if (type & TYPE_GHOSTED)
4658 strcpy(value_string, "n/a");
4660 return value_string;
4663 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4667 static char token_string[MAX_LINE_LEN];
4668 int token_type = token_info[token_nr].type;
4669 void *setup_value = token_info[token_nr].value;
4670 char *token_text = token_info[token_nr].text;
4671 char *value_string = getSetupValue(token_type, setup_value);
4673 // build complete token string
4674 sprintf(token_string, "%s%s", prefix, token_text);
4676 // build setup entry line
4677 line = getFormattedSetupEntry(token_string, value_string);
4679 if (token_type == TYPE_KEY_X11)
4681 Key key = *(Key *)setup_value;
4682 char *keyname = getKeyNameFromKey(key);
4684 // add comment, if useful
4685 if (!strEqual(keyname, "(undefined)") &&
4686 !strEqual(keyname, "(unknown)"))
4688 // add at least one whitespace
4690 for (i = strlen(line); i < token_comment_position; i++)
4694 strcat(line, keyname);
4701 static void InitLastPlayedLevels_ParentNode(void)
4703 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4704 LevelDirTree *leveldir_new = NULL;
4706 // check if parent node for last played levels already exists
4707 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4710 leveldir_new = newTreeInfo();
4712 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4714 leveldir_new->level_group = TRUE;
4715 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4717 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4718 setString(&leveldir_new->name, "<< (last played level sets)");
4719 setString(&leveldir_new->name_sorting, leveldir_new->name);
4721 pushTreeInfo(leveldir_top, leveldir_new);
4723 // create node to link back to current level directory
4724 createParentTreeInfoNode(leveldir_new);
4727 void UpdateLastPlayedLevels_TreeInfo(void)
4729 char **last_level_series = setup.level_setup.last_level_series;
4730 LevelDirTree *leveldir_last;
4731 TreeInfo **node_new = NULL;
4734 if (last_level_series[0] == NULL)
4737 InitLastPlayedLevels_ParentNode();
4739 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4740 TOKEN_STR_LAST_LEVEL_SERIES,
4741 TREE_NODE_TYPE_GROUP);
4742 if (leveldir_last == NULL)
4745 node_new = &leveldir_last->node_group->next;
4747 freeTreeInfo(*node_new);
4751 for (i = 0; last_level_series[i] != NULL; i++)
4753 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4754 last_level_series[i]);
4755 if (node_last == NULL)
4758 *node_new = getTreeInfoCopy(node_last); // copy complete node
4760 (*node_new)->node_top = &leveldir_first; // correct top node link
4761 (*node_new)->node_parent = leveldir_last; // correct parent node link
4763 (*node_new)->is_copy = TRUE; // mark entry as node copy
4765 (*node_new)->node_group = NULL;
4766 (*node_new)->next = NULL;
4768 (*node_new)->cl_first = -1; // force setting tree cursor
4770 node_new = &((*node_new)->next);
4774 static void UpdateLastPlayedLevels_List(void)
4776 char **last_level_series = setup.level_setup.last_level_series;
4777 int pos = MAX_LEVELDIR_HISTORY - 1;
4780 // search for potentially already existing entry in list of level sets
4781 for (i = 0; last_level_series[i] != NULL; i++)
4782 if (strEqual(last_level_series[i], leveldir_current->identifier))
4785 // move list of level sets one entry down (using potentially free entry)
4786 for (i = pos; i > 0; i--)
4787 setString(&last_level_series[i], last_level_series[i - 1]);
4789 // put last played level set at top position
4790 setString(&last_level_series[0], leveldir_current->identifier);
4793 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4795 static char *identifier = NULL;
4799 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4801 return NULL; // not used
4805 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4807 TREE_NODE_TYPE_COPY);
4808 return (node_new != NULL ? node_new : node);
4812 void StoreLastPlayedLevels(TreeInfo *node)
4814 StoreOrRestoreLastPlayedLevels(node, TRUE);
4817 void RestoreLastPlayedLevels(TreeInfo **node)
4819 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4822 void LoadLevelSetup_LastSeries(void)
4824 // --------------------------------------------------------------------------
4825 // ~/.<program>/levelsetup.conf
4826 // --------------------------------------------------------------------------
4828 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4829 SetupFileHash *level_setup_hash = NULL;
4833 // always start with reliable default values
4834 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4836 // start with empty history of last played level sets
4837 setString(&setup.level_setup.last_level_series[0], NULL);
4839 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4841 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4843 if (leveldir_current == NULL)
4844 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4847 if ((level_setup_hash = loadSetupFileHash(filename)))
4849 char *last_level_series =
4850 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4852 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4854 if (leveldir_current == NULL)
4855 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4857 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4859 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4860 LevelDirTree *leveldir_last;
4862 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4864 last_level_series = getHashEntry(level_setup_hash, token);
4866 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4868 if (leveldir_last != NULL)
4869 setString(&setup.level_setup.last_level_series[pos++],
4873 setString(&setup.level_setup.last_level_series[pos], NULL);
4875 freeSetupFileHash(level_setup_hash);
4879 Debug("setup", "using default setup values");
4885 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
4887 // --------------------------------------------------------------------------
4888 // ~/.<program>/levelsetup.conf
4889 // --------------------------------------------------------------------------
4891 // check if the current level directory structure is available at this point
4892 if (leveldir_current == NULL)
4895 char **last_level_series = setup.level_setup.last_level_series;
4896 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4900 InitUserDataDirectory();
4902 UpdateLastPlayedLevels_List();
4904 if (!(file = fopen(filename, MODE_WRITE)))
4906 Warn("cannot write setup file '%s'", filename);
4913 fprintFileHeader(file, LEVELSETUP_FILENAME);
4915 if (deactivate_last_level_series)
4916 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
4918 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
4919 leveldir_current->identifier));
4921 for (i = 0; last_level_series[i] != NULL; i++)
4923 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
4925 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4927 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
4932 SetFilePermissions(filename, PERMS_PRIVATE);
4937 void SaveLevelSetup_LastSeries(void)
4939 SaveLevelSetup_LastSeries_Ext(FALSE);
4942 void SaveLevelSetup_LastSeries_Deactivate(void)
4944 SaveLevelSetup_LastSeries_Ext(TRUE);
4947 static void checkSeriesInfo(void)
4949 static char *level_directory = NULL;
4952 DirectoryEntry *dir_entry;
4955 checked_free(level_directory);
4957 // check for more levels besides the 'levels' field of 'levelinfo.conf'
4959 level_directory = getPath2((leveldir_current->in_user_dir ?
4960 getUserLevelDir(NULL) :
4961 options.level_directory),
4962 leveldir_current->fullpath);
4964 if ((dir = openDirectory(level_directory)) == NULL)
4966 Warn("cannot read level directory '%s'", level_directory);
4972 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4974 if (strlen(dir_entry->basename) > 4 &&
4975 dir_entry->basename[3] == '.' &&
4976 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
4978 char levelnum_str[4];
4981 strncpy(levelnum_str, dir_entry->basename, 3);
4982 levelnum_str[3] = '\0';
4984 levelnum_value = atoi(levelnum_str);
4986 if (levelnum_value < leveldir_current->first_level)
4988 Warn("additional level %d found", levelnum_value);
4990 leveldir_current->first_level = levelnum_value;
4992 else if (levelnum_value > leveldir_current->last_level)
4994 Warn("additional level %d found", levelnum_value);
4996 leveldir_current->last_level = levelnum_value;
5002 closeDirectory(dir);
5005 void LoadLevelSetup_SeriesInfo(void)
5008 SetupFileHash *level_setup_hash = NULL;
5009 char *level_subdir = leveldir_current->subdir;
5012 // always start with reliable default values
5013 level_nr = leveldir_current->first_level;
5015 for (i = 0; i < MAX_LEVELS; i++)
5017 LevelStats_setPlayed(i, 0);
5018 LevelStats_setSolved(i, 0);
5023 // --------------------------------------------------------------------------
5024 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5025 // --------------------------------------------------------------------------
5027 level_subdir = leveldir_current->subdir;
5029 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5031 if ((level_setup_hash = loadSetupFileHash(filename)))
5035 // get last played level in this level set
5037 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5041 level_nr = atoi(token_value);
5043 if (level_nr < leveldir_current->first_level)
5044 level_nr = leveldir_current->first_level;
5045 if (level_nr > leveldir_current->last_level)
5046 level_nr = leveldir_current->last_level;
5049 // get handicap level in this level set
5051 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5055 int level_nr = atoi(token_value);
5057 if (level_nr < leveldir_current->first_level)
5058 level_nr = leveldir_current->first_level;
5059 if (level_nr > leveldir_current->last_level + 1)
5060 level_nr = leveldir_current->last_level;
5062 if (leveldir_current->user_defined || !leveldir_current->handicap)
5063 level_nr = leveldir_current->last_level;
5065 leveldir_current->handicap_level = level_nr;
5068 // get number of played and solved levels in this level set
5070 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5072 char *token = HASH_ITERATION_TOKEN(itr);
5073 char *value = HASH_ITERATION_VALUE(itr);
5075 if (strlen(token) == 3 &&
5076 token[0] >= '0' && token[0] <= '9' &&
5077 token[1] >= '0' && token[1] <= '9' &&
5078 token[2] >= '0' && token[2] <= '9')
5080 int level_nr = atoi(token);
5083 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5085 value = strchr(value, ' ');
5088 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5091 END_HASH_ITERATION(hash, itr)
5093 freeSetupFileHash(level_setup_hash);
5097 Debug("setup", "using default setup values");
5103 void SaveLevelSetup_SeriesInfo(void)
5106 char *level_subdir = leveldir_current->subdir;
5107 char *level_nr_str = int2str(level_nr, 0);
5108 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5112 // --------------------------------------------------------------------------
5113 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5114 // --------------------------------------------------------------------------
5116 InitLevelSetupDirectory(level_subdir);
5118 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5120 if (!(file = fopen(filename, MODE_WRITE)))
5122 Warn("cannot write setup file '%s'", filename);
5129 fprintFileHeader(file, LEVELSETUP_FILENAME);
5131 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5133 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5134 handicap_level_str));
5136 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5139 if (LevelStats_getPlayed(i) > 0 ||
5140 LevelStats_getSolved(i) > 0)
5145 sprintf(token, "%03d", i);
5146 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5148 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5154 SetFilePermissions(filename, PERMS_PRIVATE);
5159 int LevelStats_getPlayed(int nr)
5161 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5164 int LevelStats_getSolved(int nr)
5166 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5169 void LevelStats_setPlayed(int nr, int value)
5171 if (nr >= 0 && nr < MAX_LEVELS)
5172 level_stats[nr].played = value;
5175 void LevelStats_setSolved(int nr, int value)
5177 if (nr >= 0 && nr < MAX_LEVELS)
5178 level_stats[nr].solved = value;
5181 void LevelStats_incPlayed(int nr)
5183 if (nr >= 0 && nr < MAX_LEVELS)
5184 level_stats[nr].played++;
5187 void LevelStats_incSolved(int nr)
5189 if (nr >= 0 && nr < MAX_LEVELS)
5190 level_stats[nr].solved++;
5193 void LoadUserSetup(void)
5195 // --------------------------------------------------------------------------
5196 // ~/.<program>/usersetup.conf
5197 // --------------------------------------------------------------------------
5199 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5200 SetupFileHash *user_setup_hash = NULL;
5202 // always start with reliable default values
5205 if ((user_setup_hash = loadSetupFileHash(filename)))
5209 // get last selected user number
5210 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5213 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5215 freeSetupFileHash(user_setup_hash);
5219 Debug("setup", "using default setup values");
5225 void SaveUserSetup(void)
5227 // --------------------------------------------------------------------------
5228 // ~/.<program>/usersetup.conf
5229 // --------------------------------------------------------------------------
5231 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5234 InitMainUserDataDirectory();
5236 if (!(file = fopen(filename, MODE_WRITE)))
5238 Warn("cannot write setup file '%s'", filename);
5245 fprintFileHeader(file, USERSETUP_FILENAME);
5247 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5251 SetFilePermissions(filename, PERMS_PRIVATE);