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>
27 #include "zip/miniunz.h"
30 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
31 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
33 #define NUM_LEVELCLASS_DESC 8
35 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
47 #define TOKEN_VALUE_POSITION_SHORT 32
48 #define TOKEN_VALUE_POSITION_DEFAULT 40
49 #define TOKEN_COMMENT_POSITION_DEFAULT 60
51 #define TREE_NODE_TYPE_DEFAULT 0
52 #define TREE_NODE_TYPE_PARENT 1
53 #define TREE_NODE_TYPE_GROUP 2
54 #define TREE_NODE_TYPE_COPY 3
56 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
57 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
58 ti->is_copy ? TREE_NODE_TYPE_COPY : \
59 TREE_NODE_TYPE_DEFAULT)
62 static void setTreeInfoToDefaults(TreeInfo *, int);
63 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
64 static int compareTreeInfoEntries(const void *, const void *);
66 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
67 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
69 static SetupFileHash *artworkinfo_cache_old = NULL;
70 static SetupFileHash *artworkinfo_cache_new = NULL;
71 static SetupFileHash *optional_tokens_hash = NULL;
72 static SetupFileHash *missing_file_hash = NULL;
73 static boolean use_artworkinfo_cache = TRUE;
74 static boolean update_artworkinfo_cache = FALSE;
77 // ----------------------------------------------------------------------------
79 // ----------------------------------------------------------------------------
81 static void WarnUsingFallback(char *filename)
83 if (getHashEntry(missing_file_hash, filename) == NULL)
85 setHashEntry(missing_file_hash, filename, "");
87 Debug("setup", "cannot find artwork file '%s' (using fallback)", filename);
91 static char *getLevelClassDescription(TreeInfo *ti)
93 int position = ti->sort_priority / 100;
95 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
96 return levelclass_desc[position];
98 return "Unknown Level Class";
101 static char *getCacheDir(void)
103 static char *cache_dir = NULL;
105 if (cache_dir == NULL)
106 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
111 static char *getScoreDir(char *level_subdir)
113 static char *score_dir = NULL;
114 static char *score_level_dir = NULL;
115 char *score_subdir = SCORES_DIRECTORY;
117 if (score_dir == NULL)
118 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
120 if (level_subdir != NULL)
122 checked_free(score_level_dir);
124 score_level_dir = getPath2(score_dir, level_subdir);
126 return score_level_dir;
132 static char *getScoreCacheDir(char *level_subdir)
134 static char *score_dir = NULL;
135 static char *score_level_dir = NULL;
136 char *score_subdir = SCORES_DIRECTORY;
138 if (score_dir == NULL)
139 score_dir = getPath2(getCacheDir(), score_subdir);
141 if (level_subdir != NULL)
143 checked_free(score_level_dir);
145 score_level_dir = getPath2(score_dir, level_subdir);
147 return score_level_dir;
153 static char *getScoreTapeDir(char *level_subdir, int nr)
155 static char *score_tape_dir = NULL;
156 char tape_subdir[MAX_FILENAME_LEN];
158 checked_free(score_tape_dir);
160 sprintf(tape_subdir, "%03d", nr);
161 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
163 return score_tape_dir;
166 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
168 static char *score_cache_tape_dir = NULL;
169 char tape_subdir[MAX_FILENAME_LEN];
171 checked_free(score_cache_tape_dir);
173 sprintf(tape_subdir, "%03d", nr);
174 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
176 return score_cache_tape_dir;
179 static char *getUserSubdir(int nr)
181 static char user_subdir[16] = { 0 };
183 sprintf(user_subdir, "%03d", nr);
188 static char *getUserDir(int nr)
190 static char *user_dir = NULL;
191 char *main_data_dir = getMainUserGameDataDir();
192 char *users_subdir = USERS_DIRECTORY;
193 char *user_subdir = getUserSubdir(nr);
195 checked_free(user_dir);
198 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
200 user_dir = getPath2(main_data_dir, users_subdir);
205 static char *getLevelSetupDir(char *level_subdir)
207 static char *levelsetup_dir = NULL;
208 char *data_dir = getUserGameDataDir();
209 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
211 checked_free(levelsetup_dir);
213 if (level_subdir != NULL)
214 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
216 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
218 return levelsetup_dir;
221 static char *getNetworkDir(void)
223 static char *network_dir = NULL;
225 if (network_dir == NULL)
226 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
231 char *getLevelDirFromTreeInfo(TreeInfo *node)
233 static char *level_dir = NULL;
236 return options.level_directory;
238 checked_free(level_dir);
240 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
241 options.level_directory), node->fullpath);
246 char *getUserLevelDir(char *level_subdir)
248 static char *userlevel_dir = NULL;
249 char *data_dir = getMainUserGameDataDir();
250 char *userlevel_subdir = LEVELS_DIRECTORY;
252 checked_free(userlevel_dir);
254 if (level_subdir != NULL)
255 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
257 userlevel_dir = getPath2(data_dir, userlevel_subdir);
259 return userlevel_dir;
262 char *getNetworkLevelDir(char *level_subdir)
264 static char *network_level_dir = NULL;
265 char *data_dir = getNetworkDir();
266 char *networklevel_subdir = LEVELS_DIRECTORY;
268 checked_free(network_level_dir);
270 if (level_subdir != NULL)
271 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
273 network_level_dir = getPath2(data_dir, networklevel_subdir);
275 return network_level_dir;
278 char *getCurrentLevelDir(void)
280 return getLevelDirFromTreeInfo(leveldir_current);
283 char *getNewUserLevelSubdir(void)
285 static char *new_level_subdir = NULL;
286 char *subdir_prefix = getLoginName();
287 char subdir_suffix[10];
288 int max_suffix_number = 1000;
291 while (++i < max_suffix_number)
293 sprintf(subdir_suffix, "_%d", i);
295 checked_free(new_level_subdir);
296 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
298 if (!directoryExists(getUserLevelDir(new_level_subdir)))
302 return new_level_subdir;
305 char *getTapeDir(char *level_subdir)
307 static char *tape_dir = NULL;
308 char *data_dir = getUserGameDataDir();
309 char *tape_subdir = TAPES_DIRECTORY;
311 checked_free(tape_dir);
313 if (level_subdir != NULL)
314 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
316 tape_dir = getPath2(data_dir, tape_subdir);
321 static char *getSolutionTapeDir(void)
323 static char *tape_dir = NULL;
324 char *data_dir = getCurrentLevelDir();
325 char *tape_subdir = TAPES_DIRECTORY;
327 checked_free(tape_dir);
329 tape_dir = getPath2(data_dir, tape_subdir);
334 static char *getDefaultGraphicsDir(char *graphics_subdir)
336 static char *graphics_dir = NULL;
338 if (graphics_subdir == NULL)
339 return options.graphics_directory;
341 checked_free(graphics_dir);
343 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
348 static char *getDefaultSoundsDir(char *sounds_subdir)
350 static char *sounds_dir = NULL;
352 if (sounds_subdir == NULL)
353 return options.sounds_directory;
355 checked_free(sounds_dir);
357 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
362 static char *getDefaultMusicDir(char *music_subdir)
364 static char *music_dir = NULL;
366 if (music_subdir == NULL)
367 return options.music_directory;
369 checked_free(music_dir);
371 music_dir = getPath2(options.music_directory, music_subdir);
376 static char *getClassicArtworkSet(int type)
378 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
379 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
380 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
383 static char *getClassicArtworkDir(int type)
385 return (type == TREE_TYPE_GRAPHICS_DIR ?
386 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
387 type == TREE_TYPE_SOUNDS_DIR ?
388 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
389 type == TREE_TYPE_MUSIC_DIR ?
390 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
393 char *getUserGraphicsDir(void)
395 static char *usergraphics_dir = NULL;
397 if (usergraphics_dir == NULL)
398 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
400 return usergraphics_dir;
403 char *getUserSoundsDir(void)
405 static char *usersounds_dir = NULL;
407 if (usersounds_dir == NULL)
408 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
410 return usersounds_dir;
413 char *getUserMusicDir(void)
415 static char *usermusic_dir = NULL;
417 if (usermusic_dir == NULL)
418 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
420 return usermusic_dir;
423 static char *getSetupArtworkDir(TreeInfo *ti)
425 static char *artwork_dir = NULL;
430 checked_free(artwork_dir);
432 artwork_dir = getPath2(ti->basepath, ti->fullpath);
437 char *setLevelArtworkDir(TreeInfo *ti)
439 char **artwork_path_ptr, **artwork_set_ptr;
440 TreeInfo *level_artwork;
442 if (ti == NULL || leveldir_current == NULL)
445 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
446 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
448 checked_free(*artwork_path_ptr);
450 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
452 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
457 No (or non-existing) artwork configured in "levelinfo.conf". This would
458 normally result in using the artwork configured in the setup menu. But
459 if an artwork subdirectory exists (which might contain custom artwork
460 or an artwork configuration file), this level artwork must be treated
461 as relative to the default "classic" artwork, not to the artwork that
462 is currently configured in the setup menu.
464 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
465 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
466 the real "classic" artwork from the original R'n'D (like "gfx_classic").
469 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
471 checked_free(*artwork_set_ptr);
473 if (directoryExists(dir))
475 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
476 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
480 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
481 *artwork_set_ptr = NULL;
487 return *artwork_set_ptr;
490 static char *getLevelArtworkSet(int type)
492 if (leveldir_current == NULL)
495 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
498 static char *getLevelArtworkDir(int type)
500 if (leveldir_current == NULL)
501 return UNDEFINED_FILENAME;
503 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
506 char *getProgramMainDataPath(char *command_filename, char *base_path)
508 // check if the program's main data base directory is configured
509 if (!strEqual(base_path, "."))
510 return getStringCopy(base_path);
512 /* if the program is configured to start from current directory (default),
513 determine program package directory from program binary (some versions
514 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
515 set the current working directory to the program package directory) */
516 char *main_data_path = getBasePath(command_filename);
518 #if defined(PLATFORM_MAC)
519 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
521 char *main_data_path_old = main_data_path;
523 // cut relative path to Mac OS X application binary directory from path
524 main_data_path[strlen(main_data_path) -
525 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
527 // cut trailing path separator from path (but not if path is root directory)
528 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
529 main_data_path[strlen(main_data_path) - 1] = '\0';
531 // replace empty path with current directory
532 if (strEqual(main_data_path, ""))
533 main_data_path = ".";
535 // add relative path to Mac OS X application resources directory to path
536 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
538 free(main_data_path_old);
542 return main_data_path;
545 char *getProgramConfigFilename(char *command_filename)
547 static char *config_filename_1 = NULL;
548 static char *config_filename_2 = NULL;
549 static char *config_filename_3 = NULL;
550 static boolean initialized = FALSE;
554 char *command_filename_1 = getStringCopy(command_filename);
556 // strip trailing executable suffix from command filename
557 if (strSuffix(command_filename_1, ".exe"))
558 command_filename_1[strlen(command_filename_1) - 4] = '\0';
560 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
561 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
563 char *command_basepath = getBasePath(command_filename);
564 char *command_basename = getBaseNameNoSuffix(command_filename);
565 char *command_filename_2 = getPath2(command_basepath, command_basename);
567 config_filename_1 = getStringCat2(command_filename_1, ".conf");
568 config_filename_2 = getStringCat2(command_filename_2, ".conf");
569 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
571 checked_free(base_path);
572 checked_free(conf_directory);
574 checked_free(command_basepath);
575 checked_free(command_basename);
577 checked_free(command_filename_1);
578 checked_free(command_filename_2);
583 // 1st try: look for config file that exactly matches the binary filename
584 if (fileExists(config_filename_1))
585 return config_filename_1;
587 // 2nd try: look for config file that matches binary filename without suffix
588 if (fileExists(config_filename_2))
589 return config_filename_2;
591 // 3rd try: return setup config filename in global program config directory
592 return config_filename_3;
595 static char *getPlatformConfigFilename(char *config_filename)
597 static char *platform_config_filename = NULL;
598 static boolean initialized = FALSE;
602 char *config_basepath = getBasePath(config_filename);
603 char *config_basename = getBaseNameNoSuffix(config_filename);
604 char *config_filename_prefix = getPath2(config_basepath, config_basename);
605 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
606 char *platform_suffix = getStringCat2("-", platform_string_lower);
608 platform_config_filename = getStringCat3(config_filename_prefix,
609 platform_suffix, ".conf");
611 checked_free(config_basepath);
612 checked_free(config_basename);
613 checked_free(config_filename_prefix);
614 checked_free(platform_string_lower);
615 checked_free(platform_suffix);
620 return platform_config_filename;
623 char *getTapeFilename(int nr)
625 static char *filename = NULL;
626 char basename[MAX_FILENAME_LEN];
628 checked_free(filename);
630 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
631 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
636 char *getTemporaryTapeFilename(void)
638 static char *filename = NULL;
639 char basename[MAX_FILENAME_LEN];
641 checked_free(filename);
643 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
644 filename = getPath2(getTapeDir(NULL), basename);
649 char *getDefaultSolutionTapeFilename(int nr)
651 static char *filename = NULL;
652 char basename[MAX_FILENAME_LEN];
654 checked_free(filename);
656 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
657 filename = getPath2(getSolutionTapeDir(), basename);
662 char *getSokobanSolutionTapeFilename(int nr)
664 static char *filename = NULL;
665 char basename[MAX_FILENAME_LEN];
667 checked_free(filename);
669 sprintf(basename, "%03d.sln", nr);
670 filename = getPath2(getSolutionTapeDir(), basename);
675 char *getSolutionTapeFilename(int nr)
677 char *filename = getDefaultSolutionTapeFilename(nr);
679 if (!fileExists(filename))
681 char *filename2 = getSokobanSolutionTapeFilename(nr);
683 if (fileExists(filename2))
690 char *getScoreFilename(int nr)
692 static char *filename = NULL;
693 char basename[MAX_FILENAME_LEN];
695 checked_free(filename);
697 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
699 // used instead of "leveldir_current->subdir" (for network games)
700 filename = getPath2(getScoreDir(levelset.identifier), basename);
705 char *getScoreCacheFilename(int nr)
707 static char *filename = NULL;
708 char basename[MAX_FILENAME_LEN];
710 checked_free(filename);
712 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
714 // used instead of "leveldir_current->subdir" (for network games)
715 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
720 char *getScoreTapeBasename(char *name)
722 static char basename[MAX_FILENAME_LEN];
723 char basename_raw[MAX_FILENAME_LEN];
726 sprintf(timestamp, "%s", getCurrentTimestamp());
727 sprintf(basename_raw, "%s-%s", timestamp, name);
728 sprintf(basename, "%s-%08x", timestamp, get_hash_from_string(basename_raw));
733 char *getScoreTapeFilename(char *basename_no_ext, int nr)
735 static char *filename = NULL;
736 char basename[MAX_FILENAME_LEN];
738 checked_free(filename);
740 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
742 // used instead of "leveldir_current->subdir" (for network games)
743 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
748 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
750 static char *filename = NULL;
751 char basename[MAX_FILENAME_LEN];
753 checked_free(filename);
755 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
757 // used instead of "leveldir_current->subdir" (for network games)
758 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
763 char *getSetupFilename(void)
765 static char *filename = NULL;
767 checked_free(filename);
769 filename = getPath2(getSetupDir(), SETUP_FILENAME);
774 char *getDefaultSetupFilename(void)
776 return program.config_filename;
779 char *getPlatformSetupFilename(void)
781 return getPlatformConfigFilename(program.config_filename);
784 char *getEditorSetupFilename(void)
786 static char *filename = NULL;
788 checked_free(filename);
789 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
791 if (fileExists(filename))
794 checked_free(filename);
795 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
800 char *getFilenameFromCurrentLevelDirUpward(char *basename)
802 // global variable "leveldir_current" must be modified in the loop below
803 LevelDirTree *leveldir_current_last = leveldir_current;
804 static char *filename = NULL;
806 // check for filename in path from current to topmost tree node
808 while (leveldir_current != NULL)
810 checked_free(filename);
812 filename = getPath2(getCurrentLevelDir(), basename);
814 if (fileExists(filename))
817 leveldir_current = leveldir_current->node_parent;
820 // restore global variable "leveldir_current" modified in above loop
821 leveldir_current = leveldir_current_last;
826 char *getHelpAnimFilename(void)
828 static char *filename = NULL;
830 checked_free(filename);
832 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
837 char *getHelpTextFilename(void)
839 static char *filename = NULL;
841 checked_free(filename);
843 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
848 static char *getLevelSetInfoBasename(int nr)
850 static char basename[32];
852 sprintf(basename, "levelset_%d.txt", nr + 1);
857 char *getLevelSetInfoFilename(int nr)
859 char *basename = getLevelSetInfoBasename(nr);
860 static char *info_subdir = NULL;
861 static char *filename = NULL;
863 if (info_subdir == NULL)
864 info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY);
866 checked_free(filename);
868 // look for level set info file the current level set directory
869 filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
870 if (fileExists(filename))
890 for (i = 0; basenames[i] != NULL; i++)
892 checked_free(filename);
893 filename = getPath2(getCurrentLevelDir(), basenames[i]);
895 if (fileExists(filename))
902 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
904 static char basename[32];
906 sprintf(basename, "%s_%d.txt",
907 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
912 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
914 static char *filename = NULL;
916 boolean skip_setup_artwork = FALSE;
918 checked_free(filename);
920 basename = getLevelSetTitleMessageBasename(nr, initial);
922 if (!gfx.override_level_graphics)
924 // 1st try: look for special artwork in current level series directory
925 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
926 if (fileExists(filename))
931 // 2nd try: look for message file in current level set directory
932 filename = getPath2(getCurrentLevelDir(), basename);
933 if (fileExists(filename))
938 // check if there is special artwork configured in level series config
939 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
941 // 3rd try: look for special artwork configured in level series config
942 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
943 if (fileExists(filename))
948 // take missing artwork configured in level set config from default
949 skip_setup_artwork = TRUE;
953 if (!skip_setup_artwork)
955 // 4th try: look for special artwork in configured artwork directory
956 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
957 if (fileExists(filename))
963 // 5th try: look for default artwork in new default artwork directory
964 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
965 if (fileExists(filename))
970 // 6th try: look for default artwork in old default artwork directory
971 filename = getPath2(options.graphics_directory, basename);
972 if (fileExists(filename))
975 return NULL; // cannot find specified artwork file anywhere
978 static char *getCreditsBasename(int nr)
980 static char basename[32];
982 sprintf(basename, "credits_%d.txt", nr + 1);
987 char *getCreditsFilename(int nr, boolean global)
989 char *basename = getCreditsBasename(nr);
990 char *basepath = NULL;
991 static char *credits_subdir = NULL;
992 static char *filename = NULL;
994 if (credits_subdir == NULL)
995 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
997 checked_free(filename);
999 // look for credits file in the game's base or current level set directory
1000 basepath = (global ? options.base_directory : getCurrentLevelDir());
1002 filename = getPath3(basepath, credits_subdir, basename);
1003 if (fileExists(filename))
1006 return NULL; // cannot find credits file
1009 static char *getProgramInfoBasename(int nr)
1011 static char basename[32];
1013 sprintf(basename, "program_%d.txt", nr + 1);
1018 char *getProgramInfoFilename(int nr)
1020 char *basename = getProgramInfoBasename(nr);
1021 static char *info_subdir = NULL;
1022 static char *filename = NULL;
1024 if (info_subdir == NULL)
1025 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
1027 checked_free(filename);
1029 // look for program info file in the game's base directory
1030 filename = getPath3(options.base_directory, info_subdir, basename);
1031 if (fileExists(filename))
1034 return NULL; // cannot find program info file
1037 static char *getCorrectedArtworkBasename(char *basename)
1042 char *getCustomImageFilename(char *basename)
1044 static char *filename = NULL;
1045 boolean skip_setup_artwork = FALSE;
1047 checked_free(filename);
1049 basename = getCorrectedArtworkBasename(basename);
1051 if (!gfx.override_level_graphics)
1053 // 1st try: look for special artwork in current level series directory
1054 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1055 if (fileExists(filename))
1060 // check if there is special artwork configured in level series config
1061 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1063 // 2nd try: look for special artwork configured in level series config
1064 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1065 if (fileExists(filename))
1070 // take missing artwork configured in level set config from default
1071 skip_setup_artwork = TRUE;
1075 if (!skip_setup_artwork)
1077 // 3rd try: look for special artwork in configured artwork directory
1078 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1079 if (fileExists(filename))
1085 // 4th try: look for default artwork in new default artwork directory
1086 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1087 if (fileExists(filename))
1092 // 5th try: look for default artwork in old default artwork directory
1093 filename = getImg2(options.graphics_directory, basename);
1094 if (fileExists(filename))
1097 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1101 WarnUsingFallback(basename);
1103 // 6th try: look for fallback artwork in old default artwork directory
1104 // (needed to prevent errors when trying to access unused artwork files)
1105 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1106 if (fileExists(filename))
1110 return NULL; // cannot find specified artwork file anywhere
1113 char *getCustomSoundFilename(char *basename)
1115 static char *filename = NULL;
1116 boolean skip_setup_artwork = FALSE;
1118 checked_free(filename);
1120 basename = getCorrectedArtworkBasename(basename);
1122 if (!gfx.override_level_sounds)
1124 // 1st try: look for special artwork in current level series directory
1125 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1126 if (fileExists(filename))
1131 // check if there is special artwork configured in level series config
1132 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1134 // 2nd try: look for special artwork configured in level series config
1135 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1136 if (fileExists(filename))
1141 // take missing artwork configured in level set config from default
1142 skip_setup_artwork = TRUE;
1146 if (!skip_setup_artwork)
1148 // 3rd try: look for special artwork in configured artwork directory
1149 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1150 if (fileExists(filename))
1156 // 4th try: look for default artwork in new default artwork directory
1157 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1158 if (fileExists(filename))
1163 // 5th try: look for default artwork in old default artwork directory
1164 filename = getPath2(options.sounds_directory, basename);
1165 if (fileExists(filename))
1168 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1172 WarnUsingFallback(basename);
1174 // 6th try: look for fallback artwork in old default artwork directory
1175 // (needed to prevent errors when trying to access unused artwork files)
1176 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1177 if (fileExists(filename))
1181 return NULL; // cannot find specified artwork file anywhere
1184 char *getCustomMusicFilename(char *basename)
1186 static char *filename = NULL;
1187 boolean skip_setup_artwork = FALSE;
1189 checked_free(filename);
1191 basename = getCorrectedArtworkBasename(basename);
1193 if (!gfx.override_level_music)
1195 // 1st try: look for special artwork in current level series directory
1196 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1197 if (fileExists(filename))
1202 // check if there is special artwork configured in level series config
1203 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1205 // 2nd try: look for special artwork configured in level series config
1206 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1207 if (fileExists(filename))
1212 // take missing artwork configured in level set config from default
1213 skip_setup_artwork = TRUE;
1217 if (!skip_setup_artwork)
1219 // 3rd try: look for special artwork in configured artwork directory
1220 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1221 if (fileExists(filename))
1227 // 4th try: look for default artwork in new default artwork directory
1228 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1229 if (fileExists(filename))
1234 // 5th try: look for default artwork in old default artwork directory
1235 filename = getPath2(options.music_directory, basename);
1236 if (fileExists(filename))
1239 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1243 WarnUsingFallback(basename);
1245 // 6th try: look for fallback artwork in old default artwork directory
1246 // (needed to prevent errors when trying to access unused artwork files)
1247 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1248 if (fileExists(filename))
1252 return NULL; // cannot find specified artwork file anywhere
1255 char *getCustomArtworkFilename(char *basename, int type)
1257 if (type == ARTWORK_TYPE_GRAPHICS)
1258 return getCustomImageFilename(basename);
1259 else if (type == ARTWORK_TYPE_SOUNDS)
1260 return getCustomSoundFilename(basename);
1261 else if (type == ARTWORK_TYPE_MUSIC)
1262 return getCustomMusicFilename(basename);
1264 return UNDEFINED_FILENAME;
1267 char *getCustomArtworkConfigFilename(int type)
1269 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1272 char *getCustomArtworkLevelConfigFilename(int type)
1274 static char *filename = NULL;
1276 checked_free(filename);
1278 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1283 static boolean directoryExists_CheckMusic(char *directory, boolean check_music)
1285 if (!directoryExists(directory))
1292 DirectoryEntry *dir_entry;
1293 int num_music = getMusicListSize();
1294 boolean music_found = FALSE;
1296 if ((dir = openDirectory(directory)) == NULL)
1299 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
1301 char *basename = dir_entry->basename;
1302 boolean music_already_used = FALSE;
1305 // skip all music files that are configured in music config file
1306 for (i = 0; i < num_music; i++)
1308 struct FileInfo *music = getMusicListEntry(i);
1310 if (strEqual(basename, music->filename))
1312 music_already_used = TRUE;
1318 if (music_already_used)
1321 if (FileIsMusic(dir_entry->filename))
1329 closeDirectory(dir);
1334 static char *getCustomMusicDirectoryExt(boolean check_music)
1336 static char *directory = NULL;
1337 boolean skip_setup_artwork = FALSE;
1339 checked_free(directory);
1341 if (!gfx.override_level_music)
1343 // 1st try: look for special artwork in current level series directory
1344 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1345 if (directoryExists_CheckMusic(directory, check_music))
1350 // check if there is special artwork configured in level series config
1351 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1353 // 2nd try: look for special artwork configured in level series config
1354 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1356 // directory also valid if no unconfigured music found (no game music)
1357 if (directoryExists_CheckMusic(directory, FALSE))
1362 // take missing artwork configured in level set config from default
1363 skip_setup_artwork = TRUE;
1367 if (!skip_setup_artwork)
1369 // 3rd try: look for special artwork in configured artwork directory
1370 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1372 // directory also valid if no unconfigured music found (no game music)
1373 if (directoryExists_CheckMusic(directory, FALSE))
1379 // 4th try: look for default artwork in new default artwork directory
1380 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1381 if (directoryExists_CheckMusic(directory, check_music))
1386 // 5th try: look for default artwork in old default artwork directory
1387 directory = getStringCopy(options.music_directory);
1388 if (directoryExists_CheckMusic(directory, check_music))
1391 return NULL; // cannot find specified artwork file anywhere
1394 char *getCustomMusicDirectory(void)
1396 return getCustomMusicDirectoryExt(FALSE);
1399 char *getCustomMusicDirectory_NoConf(void)
1401 return getCustomMusicDirectoryExt(TRUE);
1404 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1406 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1408 touchFile(filename);
1410 checked_free(filename);
1413 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1415 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1419 checked_free(filename);
1422 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1424 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1425 boolean success = fileExists(filename);
1427 checked_free(filename);
1432 void InitMissingFileHash(void)
1434 if (missing_file_hash == NULL)
1435 freeSetupFileHash(missing_file_hash);
1437 missing_file_hash = newSetupFileHash();
1440 void InitTapeDirectory(char *level_subdir)
1442 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1444 createDirectory(getUserGameDataDir(), "user data");
1445 createDirectory(getTapeDir(NULL), "main tape");
1446 createDirectory(getTapeDir(level_subdir), "level tape");
1449 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1452 void InitScoreDirectory(char *level_subdir)
1454 createDirectory(getMainUserGameDataDir(), "main user data");
1455 createDirectory(getScoreDir(NULL), "main score");
1456 createDirectory(getScoreDir(level_subdir), "level score");
1459 void InitScoreCacheDirectory(char *level_subdir)
1461 createDirectory(getMainUserGameDataDir(), "main user data");
1462 createDirectory(getCacheDir(), "cache data");
1463 createDirectory(getScoreCacheDir(NULL), "main score");
1464 createDirectory(getScoreCacheDir(level_subdir), "level score");
1467 void InitScoreTapeDirectory(char *level_subdir, int nr)
1469 InitScoreDirectory(level_subdir);
1471 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1474 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1476 InitScoreCacheDirectory(level_subdir);
1478 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1481 static void SaveUserLevelInfo(void);
1483 void InitUserLevelDirectory(char *level_subdir)
1485 if (!directoryExists(getUserLevelDir(level_subdir)))
1487 createDirectory(getMainUserGameDataDir(), "main user data");
1488 createDirectory(getUserLevelDir(NULL), "main user level");
1490 if (setup.internal.create_user_levelset)
1492 createDirectory(getUserLevelDir(level_subdir), "user level");
1494 SaveUserLevelInfo();
1499 void InitNetworkLevelDirectory(char *level_subdir)
1501 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1503 createDirectory(getMainUserGameDataDir(), "main user data");
1504 createDirectory(getNetworkDir(), "network data");
1505 createDirectory(getNetworkLevelDir(NULL), "main network level");
1506 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1510 void InitLevelSetupDirectory(char *level_subdir)
1512 createDirectory(getUserGameDataDir(), "user data");
1513 createDirectory(getLevelSetupDir(NULL), "main level setup");
1514 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1517 static void InitCacheDirectory(void)
1519 createDirectory(getMainUserGameDataDir(), "main user data");
1520 createDirectory(getCacheDir(), "cache data");
1524 // ----------------------------------------------------------------------------
1525 // some functions to handle lists of level and artwork directories
1526 // ----------------------------------------------------------------------------
1528 TreeInfo *newTreeInfo(void)
1530 return checked_calloc(sizeof(TreeInfo));
1533 TreeInfo *newTreeInfo_setDefaults(int type)
1535 TreeInfo *ti = newTreeInfo();
1537 setTreeInfoToDefaults(ti, type);
1542 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1544 node_new->next = *node_first;
1545 *node_first = node_new;
1548 void removeTreeInfo(TreeInfo **node_first)
1550 TreeInfo *node_old = *node_first;
1552 *node_first = node_old->next;
1553 node_old->next = NULL;
1555 freeTreeInfo(node_old);
1558 int numTreeInfo(TreeInfo *node)
1571 boolean validLevelSeries(TreeInfo *node)
1573 // in a number of cases, tree node is no valid level set
1574 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1580 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1582 if (validLevelSeries(node))
1584 else if (node->is_copy)
1585 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1587 return getFirstValidTreeInfoEntry(default_node);
1590 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1595 if (node->node_group) // enter node group (step down into tree)
1596 return getFirstValidTreeInfoEntry(node->node_group);
1598 if (node->parent_link) // skip first node (back link) of node group
1599 get_next_node = TRUE;
1601 if (!get_next_node) // get current regular tree node
1604 // get next regular tree node, or step up until one is found
1605 while (node->next == NULL && node->node_parent != NULL)
1606 node = node->node_parent;
1608 return getFirstValidTreeInfoEntry(node->next);
1611 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1613 return getValidTreeInfoEntryExt(node, FALSE);
1616 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1618 return getValidTreeInfoEntryExt(node, TRUE);
1621 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1626 if (node->node_parent == NULL) // top level group
1627 return *node->node_top;
1628 else // sub level group
1629 return node->node_parent->node_group;
1632 int numTreeInfoInGroup(TreeInfo *node)
1634 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1637 int getPosFromTreeInfo(TreeInfo *node)
1639 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1644 if (node_cmp == node)
1648 node_cmp = node_cmp->next;
1654 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1656 TreeInfo *node_default = node;
1668 return node_default;
1671 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1672 int node_type_wanted)
1674 if (identifier == NULL)
1679 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1680 strEqual(identifier, node->identifier))
1683 if (node->node_group)
1685 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1698 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1700 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1703 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1704 TreeInfo *node, boolean skip_sets_without_levels)
1711 if (!node->parent_link && !node->level_group &&
1712 skip_sets_without_levels && node->levels == 0)
1713 return cloneTreeNode(node_top, node_parent, node->next,
1714 skip_sets_without_levels);
1716 node_new = getTreeInfoCopy(node); // copy complete node
1718 node_new->node_top = node_top; // correct top node link
1719 node_new->node_parent = node_parent; // correct parent node link
1721 if (node->level_group)
1722 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1723 skip_sets_without_levels);
1725 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1726 skip_sets_without_levels);
1731 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1733 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1735 *ti_new = ti_cloned;
1738 static boolean adjustTreeArtworkForEMC(char **artwork_set_1,
1739 char **artwork_set_2,
1740 char **artwork_set, boolean prefer_2)
1742 // do nothing if neither special artwork set 1 nor 2 are defined
1743 if (!*artwork_set_1 && !*artwork_set_2)
1746 boolean want_1 = (prefer_2 == FALSE);
1747 boolean want_2 = (prefer_2 == TRUE);
1748 boolean has_only_1 = (!*artwork_set && !*artwork_set_2);
1749 boolean has_only_2 = (!*artwork_set && !*artwork_set_1);
1750 char *artwork_set_new = NULL;
1752 // replace missing special artwork 1 or 2 with (optional) standard artwork
1754 if (!*artwork_set_1)
1755 setString(artwork_set_1, *artwork_set);
1757 if (!*artwork_set_2)
1758 setString(artwork_set_2, *artwork_set);
1760 // set standard artwork to either special artwork 1 or 2, as requested
1762 if (*artwork_set_1 && (want_1 || has_only_1))
1763 artwork_set_new = *artwork_set_1;
1765 if (*artwork_set_2 && (want_2 || has_only_2))
1766 artwork_set_new = *artwork_set_2;
1768 if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new))
1770 setString(artwork_set, artwork_set_new);
1778 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1780 boolean settings_changed = FALSE;
1784 settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs,
1785 &node->graphics_set_aga,
1786 &node->graphics_set,
1787 setup.prefer_aga_graphics);
1788 if (node->node_group != NULL)
1789 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1794 return settings_changed;
1797 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1799 boolean settings_changed = FALSE;
1803 settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default,
1804 &node->sounds_set_lowpass,
1806 setup.prefer_lowpass_sounds);
1807 if (node->node_group != NULL)
1808 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1813 return settings_changed;
1816 int dumpTreeInfo(TreeInfo *node, int depth)
1818 char bullet_list[] = { '-', '*', 'o' };
1819 int num_leaf_nodes = 0;
1823 Debug("tree", "Dumping TreeInfo:");
1827 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1829 for (i = 0; i < depth * 2; i++)
1830 DebugContinued("", " ");
1832 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1833 bullet, node->name, node->identifier,
1834 (node->node_parent ? node->node_parent->identifier : "-"),
1835 (node->node_group ? "[GROUP]" :
1836 node->is_copy ? "[COPY]" : ""));
1838 if (!node->node_group && !node->parent_link)
1842 // use for dumping artwork info tree
1843 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1844 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1847 if (node->node_group != NULL)
1848 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1854 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1856 return num_leaf_nodes;
1859 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1860 int (*compare_function)(const void *,
1863 int num_nodes = numTreeInfo(*node_first);
1864 TreeInfo **sort_array;
1865 TreeInfo *node = *node_first;
1871 // allocate array for sorting structure pointers
1872 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1874 // writing structure pointers to sorting array
1875 while (i < num_nodes && node) // double boundary check...
1877 sort_array[i] = node;
1883 // sorting the structure pointers in the sorting array
1884 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1887 // update the linkage of list elements with the sorted node array
1888 for (i = 0; i < num_nodes - 1; i++)
1889 sort_array[i]->next = sort_array[i + 1];
1890 sort_array[num_nodes - 1]->next = NULL;
1892 // update the linkage of the main list anchor pointer
1893 *node_first = sort_array[0];
1897 // now recursively sort the level group structures
1901 if (node->node_group != NULL)
1902 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1908 void sortTreeInfo(TreeInfo **node_first)
1910 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1914 // ============================================================================
1915 // some stuff from "files.c"
1916 // ============================================================================
1918 #if defined(PLATFORM_WINDOWS)
1920 #define S_IRGRP S_IRUSR
1923 #define S_IROTH S_IRUSR
1926 #define S_IWGRP S_IWUSR
1929 #define S_IWOTH S_IWUSR
1932 #define S_IXGRP S_IXUSR
1935 #define S_IXOTH S_IXUSR
1938 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1943 #endif // PLATFORM_WINDOWS
1945 // file permissions for newly written files
1946 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1947 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1948 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1950 #define MODE_W_PRIVATE (S_IWUSR)
1951 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1952 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1954 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1955 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1956 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1958 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1959 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1960 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1963 char *getHomeDir(void)
1965 static char *dir = NULL;
1967 #if defined(PLATFORM_WINDOWS)
1970 dir = checked_malloc(MAX_PATH + 1);
1972 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1975 #elif defined(PLATFORM_EMSCRIPTEN)
1976 dir = PERSISTENT_DIRECTORY;
1977 #elif defined(PLATFORM_UNIX)
1980 if ((dir = getenv("HOME")) == NULL)
1982 dir = getUnixHomeDir();
1985 dir = getStringCopy(dir);
1997 char *getPersonalDataDir(void)
1999 static char *personal_data_dir = NULL;
2001 #if defined(PLATFORM_MAC)
2002 if (personal_data_dir == NULL)
2003 personal_data_dir = getPath2(getHomeDir(), "Documents");
2005 if (personal_data_dir == NULL)
2006 personal_data_dir = getHomeDir();
2009 return personal_data_dir;
2012 char *getMainUserGameDataDir(void)
2014 static char *main_user_data_dir = NULL;
2016 #if defined(PLATFORM_ANDROID)
2017 if (main_user_data_dir == NULL)
2018 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
2019 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
2020 SDL_AndroidGetExternalStoragePath() :
2021 SDL_AndroidGetInternalStoragePath());
2023 if (main_user_data_dir == NULL)
2024 main_user_data_dir = getPath2(getPersonalDataDir(),
2025 program.userdata_subdir);
2028 return main_user_data_dir;
2031 char *getUserGameDataDir(void)
2034 return getMainUserGameDataDir();
2036 return getUserDir(user.nr);
2039 char *getSetupDir(void)
2041 return getUserGameDataDir();
2044 static mode_t posix_umask(mode_t mask)
2046 #if defined(PLATFORM_UNIX)
2053 static int posix_mkdir(const char *pathname, mode_t mode)
2055 #if defined(PLATFORM_WINDOWS)
2056 return mkdir(pathname);
2058 return mkdir(pathname, mode);
2062 static boolean posix_process_running_setgid(void)
2064 #if defined(PLATFORM_UNIX)
2065 return (getgid() != getegid());
2071 void createDirectory(char *dir, char *text)
2073 if (directoryExists(dir))
2076 // leave "other" permissions in umask untouched, but ensure group parts
2077 // of USERDATA_DIR_MODE are not masked
2078 int permission_class = PERMS_PRIVATE;
2079 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2080 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2081 mode_t last_umask = posix_umask(0);
2082 mode_t group_umask = ~(dir_mode & S_IRWXG);
2083 int running_setgid = posix_process_running_setgid();
2085 if (permission_class == PERMS_PUBLIC)
2087 // if we're setgid, protect files against "other"
2088 // else keep umask(0) to make the dir world-writable
2091 posix_umask(last_umask & group_umask);
2093 dir_mode = DIR_PERMS_PUBLIC_ALL;
2096 if (posix_mkdir(dir, dir_mode) != 0)
2097 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2099 if (permission_class == PERMS_PUBLIC && !running_setgid)
2100 chmod(dir, dir_mode);
2102 posix_umask(last_umask); // restore previous umask
2105 void InitMainUserDataDirectory(void)
2107 createDirectory(getMainUserGameDataDir(), "main user data");
2110 void InitUserDataDirectory(void)
2112 createDirectory(getMainUserGameDataDir(), "main user data");
2116 createDirectory(getUserDir(-1), "users");
2117 createDirectory(getUserDir(user.nr), "user data");
2121 void SetFilePermissions(char *filename, int permission_class)
2123 int running_setgid = posix_process_running_setgid();
2124 int perms = (permission_class == PERMS_PRIVATE ?
2125 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2127 if (permission_class == PERMS_PUBLIC && !running_setgid)
2128 perms = FILE_PERMS_PUBLIC_ALL;
2130 chmod(filename, perms);
2133 void fprintFileHeader(FILE *file, char *basename)
2135 char *prefix = "# ";
2138 fprintf_line_with_prefix(file, prefix, sep1, 77);
2139 fprintf(file, "%s%s\n", prefix, basename);
2140 fprintf_line_with_prefix(file, prefix, sep1, 77);
2141 fprintf(file, "\n");
2144 int getFileVersionFromCookieString(const char *cookie)
2146 const char *ptr_cookie1, *ptr_cookie2;
2147 const char *pattern1 = "_FILE_VERSION_";
2148 const char *pattern2 = "?.?";
2149 const int len_cookie = strlen(cookie);
2150 const int len_pattern1 = strlen(pattern1);
2151 const int len_pattern2 = strlen(pattern2);
2152 const int len_pattern = len_pattern1 + len_pattern2;
2153 int version_super, version_major;
2155 if (len_cookie <= len_pattern)
2158 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2159 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2161 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2164 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2165 ptr_cookie2[1] != '.' ||
2166 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2169 version_super = ptr_cookie2[0] - '0';
2170 version_major = ptr_cookie2[2] - '0';
2172 return VERSION_IDENT(version_super, version_major, 0, 0);
2175 boolean checkCookieString(const char *cookie, const char *template)
2177 const char *pattern = "_FILE_VERSION_?.?";
2178 const int len_cookie = strlen(cookie);
2179 const int len_template = strlen(template);
2180 const int len_pattern = strlen(pattern);
2182 if (len_cookie != len_template)
2185 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2192 // ----------------------------------------------------------------------------
2193 // setup file list and hash handling functions
2194 // ----------------------------------------------------------------------------
2196 char *getFormattedSetupEntry(char *token, char *value)
2199 static char entry[MAX_LINE_LEN];
2201 // if value is an empty string, just return token without value
2205 // start with the token and some spaces to format output line
2206 sprintf(entry, "%s:", token);
2207 for (i = strlen(entry); i < token_value_position; i++)
2210 // continue with the token's value
2211 strcat(entry, value);
2216 SetupFileList *newSetupFileList(char *token, char *value)
2218 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2220 new->token = getStringCopy(token);
2221 new->value = getStringCopy(value);
2228 void freeSetupFileList(SetupFileList *list)
2233 checked_free(list->token);
2234 checked_free(list->value);
2237 freeSetupFileList(list->next);
2242 char *getListEntry(SetupFileList *list, char *token)
2247 if (strEqual(list->token, token))
2250 return getListEntry(list->next, token);
2253 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2258 if (strEqual(list->token, token))
2260 checked_free(list->value);
2262 list->value = getStringCopy(value);
2266 else if (list->next == NULL)
2267 return (list->next = newSetupFileList(token, value));
2269 return setListEntry(list->next, token, value);
2272 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2277 if (list->next == NULL)
2278 return (list->next = newSetupFileList(token, value));
2280 return addListEntry(list->next, token, value);
2283 #if ENABLE_UNUSED_CODE
2285 static void printSetupFileList(SetupFileList *list)
2290 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2291 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2293 printSetupFileList(list->next);
2299 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2300 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2301 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2302 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2304 #define insert_hash_entry hashtable_insert
2305 #define search_hash_entry hashtable_search
2306 #define change_hash_entry hashtable_change
2307 #define remove_hash_entry hashtable_remove
2310 unsigned int get_hash_from_string(void *key)
2315 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2316 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2317 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2318 it works better than many other constants, prime or not) has never been
2319 adequately explained.
2321 If you just want to have a good hash function, and cannot wait, djb2
2322 is one of the best string hash functions i know. It has excellent
2323 distribution and speed on many different sets of keys and table sizes.
2324 You are not likely to do better with one of the "well known" functions
2325 such as PJW, K&R, etc.
2327 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2330 char *str = (char *)key;
2331 unsigned int hash = 5381;
2334 while ((c = *str++))
2335 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2340 unsigned int get_hash_from_integer(void *key)
2342 unsigned int hash = PTR_TO_UINT(key);
2347 int hash_key_strings_are_equal(void *key1, void *key2)
2349 return (strEqual((char *)key1, (char *)key2));
2352 int hash_key_integers_are_equal(void *key1, void *key2)
2354 return (key1 == key2);
2357 SetupFileHash *newSetupFileHash(void)
2359 SetupFileHash *new_hash =
2360 create_hashtable(get_hash_from_string, hash_key_strings_are_equal, free, free);
2362 if (new_hash == NULL)
2363 Fail("create_hashtable() failed -- out of memory");
2368 void freeSetupFileHash(SetupFileHash *hash)
2373 hashtable_destroy(hash);
2376 char *getHashEntry(SetupFileHash *hash, char *token)
2381 return search_hash_entry(hash, token);
2384 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2391 value_copy = getStringCopy(value);
2393 // change value; if it does not exist, insert it as new
2394 if (!change_hash_entry(hash, token, value_copy))
2395 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2396 Fail("cannot insert into hash -- aborting");
2399 void removeHashEntry(SetupFileHash *hash, char *token)
2404 remove_hash_entry(hash, token);
2407 #if ENABLE_UNUSED_CODE
2409 static void printSetupFileHash(SetupFileHash *hash)
2411 BEGIN_HASH_ITERATION(hash, itr)
2413 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2414 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2416 END_HASH_ITERATION(hash, itr)
2421 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2422 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2423 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2425 static boolean token_value_separator_found = FALSE;
2426 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2427 static boolean token_value_separator_warning = FALSE;
2429 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2430 static boolean token_already_exists_warning = FALSE;
2433 static boolean getTokenValueFromSetupLineExt(char *line,
2434 char **token_ptr, char **value_ptr,
2435 char *filename, char *line_raw,
2437 boolean separator_required)
2439 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2440 char *token, *value, *line_ptr;
2442 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2443 if (line_raw == NULL)
2445 strncpy(line_copy, line, MAX_LINE_LEN);
2446 line_copy[MAX_LINE_LEN] = '\0';
2449 strcpy(line_raw_copy, line_copy);
2450 line_raw = line_raw_copy;
2453 // cut trailing comment from input line
2454 for (line_ptr = line; *line_ptr; line_ptr++)
2456 if (*line_ptr == '#')
2463 // cut trailing whitespaces from input line
2464 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2465 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2468 // ignore empty lines
2472 // cut leading whitespaces from token
2473 for (token = line; *token; token++)
2474 if (*token != ' ' && *token != '\t')
2477 // start with empty value as reliable default
2480 token_value_separator_found = FALSE;
2482 // find end of token to determine start of value
2483 for (line_ptr = token; *line_ptr; line_ptr++)
2485 // first look for an explicit token/value separator, like ':' or '='
2486 if (*line_ptr == ':' || *line_ptr == '=')
2488 *line_ptr = '\0'; // terminate token string
2489 value = line_ptr + 1; // set beginning of value
2491 token_value_separator_found = TRUE;
2497 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2498 // fallback: if no token/value separator found, also allow whitespaces
2499 if (!token_value_separator_found && !separator_required)
2501 for (line_ptr = token; *line_ptr; line_ptr++)
2503 if (*line_ptr == ' ' || *line_ptr == '\t')
2505 *line_ptr = '\0'; // terminate token string
2506 value = line_ptr + 1; // set beginning of value
2508 token_value_separator_found = TRUE;
2514 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2515 if (token_value_separator_found)
2517 if (!token_value_separator_warning)
2519 Debug("setup", "---");
2521 if (filename != NULL)
2523 Debug("setup", "missing token/value separator(s) in config file:");
2524 Debug("setup", "- config file: '%s'", filename);
2528 Debug("setup", "missing token/value separator(s):");
2531 token_value_separator_warning = TRUE;
2534 if (filename != NULL)
2535 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2537 Debug("setup", "- line: '%s'", line_raw);
2543 // cut trailing whitespaces from token
2544 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2545 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2548 // cut leading whitespaces from value
2549 for (; *value; value++)
2550 if (*value != ' ' && *value != '\t')
2559 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2561 // while the internal (old) interface does not require a token/value
2562 // separator (for downwards compatibility with existing files which
2563 // don't use them), it is mandatory for the external (new) interface
2565 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2568 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2569 boolean top_recursion_level, boolean is_hash)
2571 static SetupFileHash *include_filename_hash = NULL;
2572 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2573 char *token, *value, *line_ptr;
2574 void *insert_ptr = NULL;
2575 boolean read_continued_line = FALSE;
2577 int line_nr = 0, token_count = 0, include_count = 0;
2579 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2580 token_value_separator_warning = FALSE;
2583 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2584 token_already_exists_warning = FALSE;
2587 if (!(file = openFile(filename, MODE_READ)))
2589 #if DEBUG_NO_CONFIG_FILE
2590 Debug("setup", "cannot open configuration file '%s'", filename);
2596 // use "insert pointer" to store list end for constant insertion complexity
2598 insert_ptr = setup_file_data;
2600 // on top invocation, create hash to mark included files (to prevent loops)
2601 if (top_recursion_level)
2602 include_filename_hash = newSetupFileHash();
2604 // mark this file as already included (to prevent including it again)
2605 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2607 while (!checkEndOfFile(file))
2609 // read next line of input file
2610 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2613 // check if line was completely read and is terminated by line break
2614 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2617 // cut trailing line break (this can be newline and/or carriage return)
2618 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2619 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2622 // copy raw input line for later use (mainly debugging output)
2623 strcpy(line_raw, line);
2625 if (read_continued_line)
2627 // append new line to existing line, if there is enough space
2628 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2629 strcat(previous_line, line_ptr);
2631 strcpy(line, previous_line); // copy storage buffer to line
2633 read_continued_line = FALSE;
2636 // if the last character is '\', continue at next line
2637 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2639 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2640 strcpy(previous_line, line); // copy line to storage buffer
2642 read_continued_line = TRUE;
2647 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2648 line_raw, line_nr, FALSE))
2653 if (strEqual(token, "include"))
2655 if (getHashEntry(include_filename_hash, value) == NULL)
2657 char *basepath = getBasePath(filename);
2658 char *basename = getBaseName(value);
2659 char *filename_include = getPath2(basepath, basename);
2661 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2665 free(filename_include);
2671 Warn("ignoring already processed file '%s'", value);
2678 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2680 getHashEntry((SetupFileHash *)setup_file_data, token);
2682 if (old_value != NULL)
2684 if (!token_already_exists_warning)
2686 Debug("setup", "---");
2687 Debug("setup", "duplicate token(s) found in config file:");
2688 Debug("setup", "- config file: '%s'", filename);
2690 token_already_exists_warning = TRUE;
2693 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2694 Debug("setup", " old value: '%s'", old_value);
2695 Debug("setup", " new value: '%s'", value);
2699 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2703 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2713 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2714 if (token_value_separator_warning)
2715 Debug("setup", "---");
2718 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2719 if (token_already_exists_warning)
2720 Debug("setup", "---");
2723 if (token_count == 0 && include_count == 0)
2724 Warn("configuration file '%s' is empty", filename);
2726 if (top_recursion_level)
2727 freeSetupFileHash(include_filename_hash);
2732 static int compareSetupFileData(const void *object1, const void *object2)
2734 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2735 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2737 return strcmp(entry1->token, entry2->token);
2740 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2742 int item_count = hashtable_count(hash);
2743 int item_size = sizeof(struct ConfigInfo);
2744 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2748 // copy string pointers from hash to array
2749 BEGIN_HASH_ITERATION(hash, itr)
2751 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2752 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2756 if (i > item_count) // should never happen
2759 END_HASH_ITERATION(hash, itr)
2761 // sort string pointers from hash in array
2762 qsort(sort_array, item_count, item_size, compareSetupFileData);
2764 if (!(file = fopen(filename, MODE_WRITE)))
2766 Warn("cannot write configuration file '%s'", filename);
2771 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2772 program.version_string));
2773 for (i = 0; i < item_count; i++)
2774 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2775 sort_array[i].value));
2778 checked_free(sort_array);
2781 SetupFileList *loadSetupFileList(char *filename)
2783 SetupFileList *setup_file_list = newSetupFileList("", "");
2784 SetupFileList *first_valid_list_entry;
2786 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2788 freeSetupFileList(setup_file_list);
2793 first_valid_list_entry = setup_file_list->next;
2795 // free empty list header
2796 setup_file_list->next = NULL;
2797 freeSetupFileList(setup_file_list);
2799 return first_valid_list_entry;
2802 SetupFileHash *loadSetupFileHash(char *filename)
2804 SetupFileHash *setup_file_hash = newSetupFileHash();
2806 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2808 freeSetupFileHash(setup_file_hash);
2813 return setup_file_hash;
2817 // ============================================================================
2819 // ============================================================================
2821 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2822 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2823 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2824 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2825 #define TOKEN_STR_LAST_USER "last_user"
2827 // level directory info
2828 #define LEVELINFO_TOKEN_IDENTIFIER 0
2829 #define LEVELINFO_TOKEN_NAME 1
2830 #define LEVELINFO_TOKEN_NAME_SORTING 2
2831 #define LEVELINFO_TOKEN_AUTHOR 3
2832 #define LEVELINFO_TOKEN_YEAR 4
2833 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2834 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2835 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2836 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2837 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2838 #define LEVELINFO_TOKEN_TESTED_BY 10
2839 #define LEVELINFO_TOKEN_LEVELS 11
2840 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2841 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2842 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2843 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2844 #define LEVELINFO_TOKEN_READONLY 16
2845 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2846 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2847 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2848 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2849 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2850 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2851 #define LEVELINFO_TOKEN_MUSIC_SET 23
2852 #define LEVELINFO_TOKEN_FILENAME 24
2853 #define LEVELINFO_TOKEN_FILETYPE 25
2854 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2855 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2856 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2857 #define LEVELINFO_TOKEN_HANDICAP 29
2858 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2859 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2860 #define LEVELINFO_TOKEN_ALLOW_SKIPPING_LEVELS 32
2861 #define LEVELINFO_TOKEN_USE_EMC_TILES 33
2862 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 34
2864 #define NUM_LEVELINFO_TOKENS 35
2866 static LevelDirTree ldi;
2868 static struct TokenInfo levelinfo_tokens[] =
2870 // level directory info
2871 { TYPE_STRING, &ldi.identifier, "identifier" },
2872 { TYPE_STRING, &ldi.name, "name" },
2873 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2874 { TYPE_STRING, &ldi.author, "author" },
2875 { TYPE_STRING, &ldi.year, "year" },
2876 { TYPE_STRING, &ldi.program_title, "program_title" },
2877 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2878 { TYPE_STRING, &ldi.program_company, "program_company" },
2879 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2880 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2881 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2882 { TYPE_INTEGER, &ldi.levels, "levels" },
2883 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2884 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2885 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2886 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2887 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2888 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.old" },
2889 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.new" },
2890 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2891 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2892 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2893 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2894 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2895 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2896 { TYPE_STRING, &ldi.music_set, "music_set" },
2897 { TYPE_STRING, &ldi.level_filename, "filename" },
2898 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2899 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2900 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2901 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2902 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2903 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2904 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2905 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2906 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2909 static struct TokenInfo artworkinfo_tokens[] =
2911 // artwork directory info
2912 { TYPE_STRING, &ldi.identifier, "identifier" },
2913 { TYPE_STRING, &ldi.subdir, "subdir" },
2914 { TYPE_STRING, &ldi.name, "name" },
2915 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2916 { TYPE_STRING, &ldi.author, "author" },
2917 { TYPE_STRING, &ldi.program_title, "program_title" },
2918 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2919 { TYPE_STRING, &ldi.program_company, "program_company" },
2920 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2921 { TYPE_STRING, &ldi.basepath, "basepath" },
2922 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2923 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2924 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2929 static char *optional_tokens[] =
2932 "program_copyright",
2938 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2942 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2943 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2944 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2945 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2948 ti->node_parent = NULL;
2949 ti->node_group = NULL;
2956 ti->fullpath = NULL;
2957 ti->basepath = NULL;
2958 ti->identifier = NULL;
2959 ti->name = getStringCopy(ANONYMOUS_NAME);
2960 ti->name_sorting = NULL;
2961 ti->author = getStringCopy(ANONYMOUS_NAME);
2964 ti->program_title = NULL;
2965 ti->program_copyright = NULL;
2966 ti->program_company = NULL;
2968 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2969 ti->latest_engine = FALSE; // default: get from level
2970 ti->parent_link = FALSE;
2971 ti->is_copy = FALSE;
2972 ti->in_user_dir = FALSE;
2973 ti->user_defined = FALSE;
2975 ti->class_desc = NULL;
2977 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2979 if (ti->type == TREE_TYPE_LEVEL_DIR)
2981 ti->imported_from = NULL;
2982 ti->imported_by = NULL;
2983 ti->tested_by = NULL;
2985 ti->graphics_set_ecs = NULL;
2986 ti->graphics_set_aga = NULL;
2987 ti->graphics_set = NULL;
2988 ti->sounds_set_default = NULL;
2989 ti->sounds_set_lowpass = NULL;
2990 ti->sounds_set = NULL;
2991 ti->music_set = NULL;
2992 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2993 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2994 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2996 ti->level_filename = NULL;
2997 ti->level_filetype = NULL;
2999 ti->special_flags = NULL;
3001 ti->empty_level_name = NULL;
3002 ti->force_level_name = FALSE;
3005 ti->first_level = 0;
3007 ti->level_group = FALSE;
3008 ti->handicap_level = 0;
3009 ti->readonly = TRUE;
3010 ti->handicap = TRUE;
3011 ti->time_limit = TRUE;
3012 ti->skip_levels = FALSE;
3014 ti->use_emc_tiles = FALSE;
3015 ti->info_screens_from_main = FALSE;
3019 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
3023 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
3025 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
3030 // copy all values from the parent structure
3032 ti->type = parent->type;
3034 ti->node_top = parent->node_top;
3035 ti->node_parent = parent;
3036 ti->node_group = NULL;
3043 ti->fullpath = NULL;
3044 ti->basepath = NULL;
3045 ti->identifier = NULL;
3046 ti->name = getStringCopy(ANONYMOUS_NAME);
3047 ti->name_sorting = NULL;
3048 ti->author = getStringCopy(parent->author);
3049 ti->year = getStringCopy(parent->year);
3051 ti->program_title = getStringCopy(parent->program_title);
3052 ti->program_copyright = getStringCopy(parent->program_copyright);
3053 ti->program_company = getStringCopy(parent->program_company);
3055 ti->sort_priority = parent->sort_priority;
3056 ti->latest_engine = parent->latest_engine;
3057 ti->parent_link = FALSE;
3058 ti->is_copy = FALSE;
3059 ti->in_user_dir = parent->in_user_dir;
3060 ti->user_defined = parent->user_defined;
3061 ti->color = parent->color;
3062 ti->class_desc = getStringCopy(parent->class_desc);
3064 ti->infotext = getStringCopy(parent->infotext);
3066 if (ti->type == TREE_TYPE_LEVEL_DIR)
3068 ti->imported_from = getStringCopy(parent->imported_from);
3069 ti->imported_by = getStringCopy(parent->imported_by);
3070 ti->tested_by = getStringCopy(parent->tested_by);
3072 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3073 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3074 ti->graphics_set = getStringCopy(parent->graphics_set);
3075 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3076 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3077 ti->sounds_set = getStringCopy(parent->sounds_set);
3078 ti->music_set = getStringCopy(parent->music_set);
3079 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3080 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3081 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3083 ti->level_filename = getStringCopy(parent->level_filename);
3084 ti->level_filetype = getStringCopy(parent->level_filetype);
3086 ti->special_flags = getStringCopy(parent->special_flags);
3088 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3089 ti->force_level_name = parent->force_level_name;
3091 ti->levels = parent->levels;
3092 ti->first_level = parent->first_level;
3093 ti->last_level = parent->last_level;
3094 ti->level_group = FALSE;
3095 ti->handicap_level = parent->handicap_level;
3096 ti->readonly = parent->readonly;
3097 ti->handicap = parent->handicap;
3098 ti->time_limit = parent->time_limit;
3099 ti->skip_levels = parent->skip_levels;
3101 ti->use_emc_tiles = parent->use_emc_tiles;
3102 ti->info_screens_from_main = parent->info_screens_from_main;
3106 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3108 TreeInfo *ti_copy = newTreeInfo();
3110 // copy all values from the original structure
3112 ti_copy->type = ti->type;
3114 ti_copy->node_top = ti->node_top;
3115 ti_copy->node_parent = ti->node_parent;
3116 ti_copy->node_group = ti->node_group;
3117 ti_copy->next = ti->next;
3119 ti_copy->cl_first = ti->cl_first;
3120 ti_copy->cl_cursor = ti->cl_cursor;
3122 ti_copy->subdir = getStringCopy(ti->subdir);
3123 ti_copy->fullpath = getStringCopy(ti->fullpath);
3124 ti_copy->basepath = getStringCopy(ti->basepath);
3125 ti_copy->identifier = getStringCopy(ti->identifier);
3126 ti_copy->name = getStringCopy(ti->name);
3127 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3128 ti_copy->author = getStringCopy(ti->author);
3129 ti_copy->year = getStringCopy(ti->year);
3131 ti_copy->program_title = getStringCopy(ti->program_title);
3132 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3133 ti_copy->program_company = getStringCopy(ti->program_company);
3135 ti_copy->imported_from = getStringCopy(ti->imported_from);
3136 ti_copy->imported_by = getStringCopy(ti->imported_by);
3137 ti_copy->tested_by = getStringCopy(ti->tested_by);
3139 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3140 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3141 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3142 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3143 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3144 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3145 ti_copy->music_set = getStringCopy(ti->music_set);
3146 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3147 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3148 ti_copy->music_path = getStringCopy(ti->music_path);
3150 ti_copy->level_filename = getStringCopy(ti->level_filename);
3151 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3153 ti_copy->special_flags = getStringCopy(ti->special_flags);
3155 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3156 ti_copy->force_level_name = ti->force_level_name;
3158 ti_copy->levels = ti->levels;
3159 ti_copy->first_level = ti->first_level;
3160 ti_copy->last_level = ti->last_level;
3161 ti_copy->sort_priority = ti->sort_priority;
3163 ti_copy->latest_engine = ti->latest_engine;
3165 ti_copy->level_group = ti->level_group;
3166 ti_copy->parent_link = ti->parent_link;
3167 ti_copy->is_copy = ti->is_copy;
3168 ti_copy->in_user_dir = ti->in_user_dir;
3169 ti_copy->user_defined = ti->user_defined;
3170 ti_copy->readonly = ti->readonly;
3171 ti_copy->handicap = ti->handicap;
3172 ti_copy->time_limit = ti->time_limit;
3173 ti_copy->skip_levels = ti->skip_levels;
3175 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3176 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3178 ti_copy->color = ti->color;
3179 ti_copy->class_desc = getStringCopy(ti->class_desc);
3180 ti_copy->handicap_level = ti->handicap_level;
3182 ti_copy->infotext = getStringCopy(ti->infotext);
3187 void freeTreeInfo(TreeInfo *ti)
3192 checked_free(ti->subdir);
3193 checked_free(ti->fullpath);
3194 checked_free(ti->basepath);
3195 checked_free(ti->identifier);
3197 checked_free(ti->name);
3198 checked_free(ti->name_sorting);
3199 checked_free(ti->author);
3200 checked_free(ti->year);
3202 checked_free(ti->program_title);
3203 checked_free(ti->program_copyright);
3204 checked_free(ti->program_company);
3206 checked_free(ti->class_desc);
3208 checked_free(ti->infotext);
3210 if (ti->type == TREE_TYPE_LEVEL_DIR)
3212 checked_free(ti->imported_from);
3213 checked_free(ti->imported_by);
3214 checked_free(ti->tested_by);
3216 checked_free(ti->graphics_set_ecs);
3217 checked_free(ti->graphics_set_aga);
3218 checked_free(ti->graphics_set);
3219 checked_free(ti->sounds_set_default);
3220 checked_free(ti->sounds_set_lowpass);
3221 checked_free(ti->sounds_set);
3222 checked_free(ti->music_set);
3224 checked_free(ti->graphics_path);
3225 checked_free(ti->sounds_path);
3226 checked_free(ti->music_path);
3228 checked_free(ti->level_filename);
3229 checked_free(ti->level_filetype);
3231 checked_free(ti->special_flags);
3234 // recursively free child node
3236 freeTreeInfo(ti->node_group);
3238 // recursively free next node
3240 freeTreeInfo(ti->next);
3245 void setSetupInfo(struct TokenInfo *token_info,
3246 int token_nr, char *token_value)
3248 int token_type = token_info[token_nr].type;
3249 void *setup_value = token_info[token_nr].value;
3251 if (token_value == NULL)
3254 // set setup field to corresponding token value
3259 *(boolean *)setup_value = get_boolean_from_string(token_value);
3262 case TYPE_SWITCH_3_STATES:
3263 *(int *)setup_value = get_switch_3_state_from_string(token_value);
3267 *(Key *)setup_value = getKeyFromKeyName(token_value);
3271 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3275 *(int *)setup_value = get_integer_from_string(token_value);
3279 checked_free(*(char **)setup_value);
3280 *(char **)setup_value = getStringCopy(token_value);
3284 *(int *)setup_value = get_player_nr_from_string(token_value);
3292 static int compareTreeInfoEntries(const void *object1, const void *object2)
3294 const TreeInfo *entry1 = *((TreeInfo **)object1);
3295 const TreeInfo *entry2 = *((TreeInfo **)object2);
3296 int tree_sorting1 = TREE_SORTING(entry1);
3297 int tree_sorting2 = TREE_SORTING(entry2);
3299 if (tree_sorting1 != tree_sorting2)
3300 return (tree_sorting1 - tree_sorting2);
3302 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3305 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3309 if (node_parent == NULL)
3312 ti_new = newTreeInfo();
3313 setTreeInfoToDefaults(ti_new, node_parent->type);
3315 ti_new->node_parent = node_parent;
3316 ti_new->parent_link = TRUE;
3318 setString(&ti_new->identifier, node_parent->identifier);
3319 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3320 setString(&ti_new->name_sorting, ti_new->name);
3322 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3323 setString(&ti_new->fullpath, node_parent->fullpath);
3325 ti_new->sort_priority = LEVELCLASS_PARENT;
3326 ti_new->latest_engine = node_parent->latest_engine;
3328 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3330 pushTreeInfo(&node_parent->node_group, ti_new);
3335 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3337 if (node_first == NULL)
3340 TreeInfo *ti_new = newTreeInfo();
3341 int type = node_first->type;
3343 setTreeInfoToDefaults(ti_new, type);
3345 ti_new->node_parent = NULL;
3346 ti_new->parent_link = FALSE;
3348 setString(&ti_new->identifier, "top_tree_node");
3349 setString(&ti_new->name, TREE_INFOTEXT(type));
3350 setString(&ti_new->name_sorting, ti_new->name);
3352 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3353 setString(&ti_new->fullpath, ".");
3355 ti_new->sort_priority = LEVELCLASS_TOP;
3356 ti_new->latest_engine = node_first->latest_engine;
3358 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3360 ti_new->node_group = node_first;
3361 ti_new->level_group = TRUE;
3363 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3365 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3366 setString(&ti_new2->name_sorting, ti_new2->name);
3371 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3375 if (node->node_group)
3376 setTreeInfoParentNodes(node->node_group, node);
3378 node->node_parent = node_parent;
3384 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3386 // add top tree node with back link node in previous tree
3387 node_first = createTopTreeInfoNode(node_first);
3389 // set all parent links (back links) in complete tree
3390 setTreeInfoParentNodes(node_first, NULL);
3396 // ----------------------------------------------------------------------------
3397 // functions for handling level and custom artwork info cache
3398 // ----------------------------------------------------------------------------
3400 static void LoadArtworkInfoCache(void)
3402 InitCacheDirectory();
3404 if (artworkinfo_cache_old == NULL)
3406 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3408 // try to load artwork info hash from already existing cache file
3409 artworkinfo_cache_old = loadSetupFileHash(filename);
3411 // try to get program version that artwork info cache was written with
3412 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3414 // check program version of artwork info cache against current version
3415 if (!strEqual(version, program.version_string))
3417 freeSetupFileHash(artworkinfo_cache_old);
3419 artworkinfo_cache_old = NULL;
3422 // if no artwork info cache file was found, start with empty hash
3423 if (artworkinfo_cache_old == NULL)
3424 artworkinfo_cache_old = newSetupFileHash();
3429 if (artworkinfo_cache_new == NULL)
3430 artworkinfo_cache_new = newSetupFileHash();
3432 update_artworkinfo_cache = FALSE;
3435 static void SaveArtworkInfoCache(void)
3437 if (!update_artworkinfo_cache)
3440 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3442 InitCacheDirectory();
3444 saveSetupFileHash(artworkinfo_cache_new, filename);
3449 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3451 static char *prefix = NULL;
3453 checked_free(prefix);
3455 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3460 // (identical to above function, but separate string buffer needed -- nasty)
3461 static char *getCacheToken(char *prefix, char *suffix)
3463 static char *token = NULL;
3465 checked_free(token);
3467 token = getStringCat2WithSeparator(prefix, suffix, ".");
3472 static char *getFileTimestampString(char *filename)
3474 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3477 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3479 struct stat file_status;
3481 if (timestamp_string == NULL)
3484 if (!fileExists(filename)) // file does not exist
3485 return (atoi(timestamp_string) != 0);
3487 if (stat(filename, &file_status) != 0) // cannot stat file
3490 return (file_status.st_mtime != atoi(timestamp_string));
3493 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3495 char *identifier = level_node->subdir;
3496 char *type_string = ARTWORK_DIRECTORY(type);
3497 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3498 char *token_main = getCacheToken(token_prefix, "CACHED");
3499 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3500 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3501 TreeInfo *artwork_info = NULL;
3503 if (!use_artworkinfo_cache)
3506 if (optional_tokens_hash == NULL)
3510 // create hash from list of optional tokens (for quick access)
3511 optional_tokens_hash = newSetupFileHash();
3512 for (i = 0; optional_tokens[i] != NULL; i++)
3513 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3520 artwork_info = newTreeInfo();
3521 setTreeInfoToDefaults(artwork_info, type);
3523 // set all structure fields according to the token/value pairs
3524 ldi = *artwork_info;
3525 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3527 char *token_suffix = artworkinfo_tokens[i].text;
3528 char *token = getCacheToken(token_prefix, token_suffix);
3529 char *value = getHashEntry(artworkinfo_cache_old, token);
3531 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3533 setSetupInfo(artworkinfo_tokens, i, value);
3535 // check if cache entry for this item is mandatory, but missing
3536 if (value == NULL && !optional)
3538 Warn("missing cache entry '%s'", token);
3544 *artwork_info = ldi;
3549 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3550 LEVELINFO_FILENAME);
3551 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3552 ARTWORKINFO_FILENAME(type));
3554 // check if corresponding "levelinfo.conf" file has changed
3555 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3556 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3558 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3561 // check if corresponding "<artworkinfo>.conf" file has changed
3562 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3563 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3565 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3568 checked_free(filename_levelinfo);
3569 checked_free(filename_artworkinfo);
3572 if (!cached && artwork_info != NULL)
3574 freeTreeInfo(artwork_info);
3579 return artwork_info;
3582 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3583 LevelDirTree *level_node, int type)
3585 char *identifier = level_node->subdir;
3586 char *type_string = ARTWORK_DIRECTORY(type);
3587 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3588 char *token_main = getCacheToken(token_prefix, "CACHED");
3589 boolean set_cache_timestamps = TRUE;
3592 setHashEntry(artworkinfo_cache_new, token_main, "true");
3594 if (set_cache_timestamps)
3596 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3597 LEVELINFO_FILENAME);
3598 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3599 ARTWORKINFO_FILENAME(type));
3600 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3601 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3603 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3604 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3606 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3607 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3609 checked_free(filename_levelinfo);
3610 checked_free(filename_artworkinfo);
3611 checked_free(timestamp_levelinfo);
3612 checked_free(timestamp_artworkinfo);
3615 ldi = *artwork_info;
3616 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3618 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3619 char *value = getSetupValue(artworkinfo_tokens[i].type,
3620 artworkinfo_tokens[i].value);
3622 setHashEntry(artworkinfo_cache_new, token, value);
3627 // ----------------------------------------------------------------------------
3628 // functions for loading level info and custom artwork info
3629 // ----------------------------------------------------------------------------
3631 int GetZipFileTreeType(char *zip_filename)
3633 static char *top_dir_path = NULL;
3634 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3635 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3637 GRAPHICSINFO_FILENAME,
3638 SOUNDSINFO_FILENAME,
3644 checked_free(top_dir_path);
3645 top_dir_path = NULL;
3647 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3649 checked_free(top_dir_conf_filename[j]);
3650 top_dir_conf_filename[j] = NULL;
3653 char **zip_entries = zip_list(zip_filename);
3655 // check if zip file successfully opened
3656 if (zip_entries == NULL || zip_entries[0] == NULL)
3657 return TREE_TYPE_UNDEFINED;
3659 // first zip file entry is expected to be top level directory
3660 char *top_dir = zip_entries[0];
3662 // check if valid top level directory found in zip file
3663 if (!strSuffix(top_dir, "/"))
3664 return TREE_TYPE_UNDEFINED;
3666 // get filenames of valid configuration files in top level directory
3667 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3668 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3670 int tree_type = TREE_TYPE_UNDEFINED;
3673 while (zip_entries[e] != NULL)
3675 // check if every zip file entry is below top level directory
3676 if (!strPrefix(zip_entries[e], top_dir))
3677 return TREE_TYPE_UNDEFINED;
3679 // check if this zip file entry is a valid configuration filename
3680 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3682 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3684 // only exactly one valid configuration file allowed
3685 if (tree_type != TREE_TYPE_UNDEFINED)
3686 return TREE_TYPE_UNDEFINED;
3698 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3701 static char *top_dir_path = NULL;
3702 static char *top_dir_conf_filename = NULL;
3704 checked_free(top_dir_path);
3705 checked_free(top_dir_conf_filename);
3707 top_dir_path = NULL;
3708 top_dir_conf_filename = NULL;
3710 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3711 ARTWORKINFO_FILENAME(tree_type));
3713 // check if valid configuration filename determined
3714 if (conf_basename == NULL || strEqual(conf_basename, ""))
3717 char **zip_entries = zip_list(zip_filename);
3719 // check if zip file successfully opened
3720 if (zip_entries == NULL || zip_entries[0] == NULL)
3723 // first zip file entry is expected to be top level directory
3724 char *top_dir = zip_entries[0];
3726 // check if valid top level directory found in zip file
3727 if (!strSuffix(top_dir, "/"))
3730 // get path of extracted top level directory
3731 top_dir_path = getPath2(directory, top_dir);
3733 // remove trailing directory separator from top level directory path
3734 // (required to be able to check for file and directory in next step)
3735 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3737 // check if zip file's top level directory already exists in target directory
3738 if (fileExists(top_dir_path)) // (checks for file and directory)
3741 // get filename of configuration file in top level directory
3742 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3744 boolean found_top_dir_conf_filename = FALSE;
3747 while (zip_entries[i] != NULL)
3749 // check if every zip file entry is below top level directory
3750 if (!strPrefix(zip_entries[i], top_dir))
3753 // check if this zip file entry is the configuration filename
3754 if (strEqual(zip_entries[i], top_dir_conf_filename))
3755 found_top_dir_conf_filename = TRUE;
3760 // check if valid configuration filename was found in zip file
3761 if (!found_top_dir_conf_filename)
3767 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3770 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3773 if (!zip_file_valid)
3775 Warn("zip file '%s' rejected!", zip_filename);
3780 char **zip_entries = zip_extract(zip_filename, directory);
3782 if (zip_entries == NULL)
3784 Warn("zip file '%s' could not be extracted!", zip_filename);
3789 Info("zip file '%s' successfully extracted!", zip_filename);
3791 // first zip file entry contains top level directory
3792 char *top_dir = zip_entries[0];
3794 // remove trailing directory separator from top level directory
3795 top_dir[strlen(top_dir) - 1] = '\0';
3800 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3803 DirectoryEntry *dir_entry;
3805 if ((dir = openDirectory(directory)) == NULL)
3807 // display error if directory is main "options.graphics_directory" etc.
3808 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3809 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3810 Warn("cannot read directory '%s'", directory);
3815 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3817 // skip non-zip files (and also directories with zip extension)
3818 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3821 char *zip_filename = getPath2(directory, dir_entry->basename);
3822 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3823 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3825 // check if zip file hasn't already been extracted or rejected
3826 if (!fileExists(zip_filename_extracted) &&
3827 !fileExists(zip_filename_rejected))
3829 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3831 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3832 zip_filename_rejected);
3835 // create empty file to mark zip file as extracted or rejected
3836 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3837 fclose(marker_file);
3840 free(zip_filename_extracted);
3841 free(zip_filename_rejected);
3845 closeDirectory(dir);
3848 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3849 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3851 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3852 TreeInfo *node_parent,
3853 char *level_directory,
3854 char *directory_name)
3856 char *directory_path = getPath2(level_directory, directory_name);
3857 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3858 SetupFileHash *setup_file_hash;
3859 LevelDirTree *leveldir_new = NULL;
3862 // unless debugging, silently ignore directories without "levelinfo.conf"
3863 if (!options.debug && !fileExists(filename))
3865 free(directory_path);
3871 setup_file_hash = loadSetupFileHash(filename);
3873 if (setup_file_hash == NULL)
3875 #if DEBUG_NO_CONFIG_FILE
3876 Debug("setup", "ignoring level directory '%s'", directory_path);
3879 free(directory_path);
3885 leveldir_new = newTreeInfo();
3888 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3890 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3892 leveldir_new->subdir = getStringCopy(directory_name);
3894 // set all structure fields according to the token/value pairs
3895 ldi = *leveldir_new;
3896 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3897 setSetupInfo(levelinfo_tokens, i,
3898 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3899 *leveldir_new = ldi;
3901 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3902 setString(&leveldir_new->name, leveldir_new->subdir);
3904 if (leveldir_new->identifier == NULL)
3905 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3907 if (leveldir_new->name_sorting == NULL)
3908 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3910 if (node_parent == NULL) // top level group
3912 leveldir_new->basepath = getStringCopy(level_directory);
3913 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3915 else // sub level group
3917 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3918 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3921 leveldir_new->last_level =
3922 leveldir_new->first_level + leveldir_new->levels - 1;
3924 leveldir_new->in_user_dir =
3925 (!strEqual(leveldir_new->basepath, options.level_directory));
3927 // adjust some settings if user's private level directory was detected
3928 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3929 leveldir_new->in_user_dir &&
3930 (strEqual(leveldir_new->subdir, getLoginName()) ||
3931 strEqual(leveldir_new->name, getLoginName()) ||
3932 strEqual(leveldir_new->author, getRealName())))
3934 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3935 leveldir_new->readonly = FALSE;
3938 leveldir_new->user_defined =
3939 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3941 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3943 leveldir_new->handicap_level = // set handicap to default value
3944 (leveldir_new->user_defined || !leveldir_new->handicap ?
3945 leveldir_new->last_level : leveldir_new->first_level);
3947 DrawInitTextItem(leveldir_new->name);
3949 pushTreeInfo(node_first, leveldir_new);
3951 freeSetupFileHash(setup_file_hash);
3953 if (leveldir_new->level_group)
3955 // create node to link back to current level directory
3956 createParentTreeInfoNode(leveldir_new);
3958 // recursively step into sub-directory and look for more level series
3959 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3960 leveldir_new, directory_path);
3963 free(directory_path);
3969 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3970 TreeInfo *node_parent,
3971 char *level_directory)
3973 // ---------- 1st stage: process any level set zip files ----------
3975 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3977 // ---------- 2nd stage: check for level set directories ----------
3980 DirectoryEntry *dir_entry;
3981 boolean valid_entry_found = FALSE;
3983 if ((dir = openDirectory(level_directory)) == NULL)
3985 Warn("cannot read level directory '%s'", level_directory);
3990 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3992 char *directory_name = dir_entry->basename;
3993 char *directory_path = getPath2(level_directory, directory_name);
3995 // skip entries for current and parent directory
3996 if (strEqual(directory_name, ".") ||
3997 strEqual(directory_name, ".."))
3999 free(directory_path);
4004 // find out if directory entry is itself a directory
4005 if (!dir_entry->is_directory) // not a directory
4007 free(directory_path);
4012 free(directory_path);
4014 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
4015 strEqual(directory_name, SOUNDS_DIRECTORY) ||
4016 strEqual(directory_name, MUSIC_DIRECTORY))
4019 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
4024 closeDirectory(dir);
4026 // special case: top level directory may directly contain "levelinfo.conf"
4027 if (node_parent == NULL && !valid_entry_found)
4029 // check if this directory directly contains a file "levelinfo.conf"
4030 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
4031 level_directory, ".");
4034 boolean valid_entry_expected =
4035 (strEqual(level_directory, options.level_directory) ||
4036 setup.internal.create_user_levelset);
4038 if (valid_entry_expected && !valid_entry_found)
4039 Warn("cannot find any valid level series in directory '%s'",
4043 boolean AdjustGraphicsForEMC(void)
4045 boolean settings_changed = FALSE;
4047 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
4048 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
4050 return settings_changed;
4053 boolean AdjustSoundsForEMC(void)
4055 boolean settings_changed = FALSE;
4057 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4058 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4060 return settings_changed;
4063 void LoadLevelInfo(void)
4065 InitUserLevelDirectory(getLoginName());
4067 DrawInitTextHead("Loading level series");
4069 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4070 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4072 leveldir_first = createTopTreeInfoNode(leveldir_first);
4074 /* after loading all level set information, clone the level directory tree
4075 and remove all level sets without levels (these may still contain artwork
4076 to be offered in the setup menu as "custom artwork", and are therefore
4077 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4078 leveldir_first_all = leveldir_first;
4079 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4081 AdjustGraphicsForEMC();
4082 AdjustSoundsForEMC();
4084 // before sorting, the first entries will be from the user directory
4085 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4087 if (leveldir_first == NULL)
4088 Fail("cannot find any valid level series in any directory");
4090 sortTreeInfo(&leveldir_first);
4092 #if ENABLE_UNUSED_CODE
4093 dumpTreeInfo(leveldir_first, 0);
4097 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4098 TreeInfo *node_parent,
4099 char *base_directory,
4100 char *directory_name, int type)
4102 char *directory_path = getPath2(base_directory, directory_name);
4103 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4104 SetupFileHash *setup_file_hash = NULL;
4105 TreeInfo *artwork_new = NULL;
4108 if (fileExists(filename))
4109 setup_file_hash = loadSetupFileHash(filename);
4111 if (setup_file_hash == NULL) // no config file -- look for artwork files
4114 DirectoryEntry *dir_entry;
4115 boolean valid_file_found = FALSE;
4117 if ((dir = openDirectory(directory_path)) != NULL)
4119 while ((dir_entry = readDirectory(dir)) != NULL)
4121 if (FileIsArtworkType(dir_entry->filename, type))
4123 valid_file_found = TRUE;
4129 closeDirectory(dir);
4132 if (!valid_file_found)
4134 #if DEBUG_NO_CONFIG_FILE
4135 if (!strEqual(directory_name, "."))
4136 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4139 free(directory_path);
4146 artwork_new = newTreeInfo();
4149 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4151 setTreeInfoToDefaults(artwork_new, type);
4153 artwork_new->subdir = getStringCopy(directory_name);
4155 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4157 // set all structure fields according to the token/value pairs
4159 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4160 setSetupInfo(levelinfo_tokens, i,
4161 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4164 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4165 setString(&artwork_new->name, artwork_new->subdir);
4167 if (artwork_new->identifier == NULL)
4168 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4170 if (artwork_new->name_sorting == NULL)
4171 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4174 if (node_parent == NULL) // top level group
4176 artwork_new->basepath = getStringCopy(base_directory);
4177 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4179 else // sub level group
4181 artwork_new->basepath = getStringCopy(node_parent->basepath);
4182 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4185 artwork_new->in_user_dir =
4186 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4188 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4190 if (setup_file_hash == NULL) // (after determining ".user_defined")
4192 if (strEqual(artwork_new->subdir, "."))
4194 if (artwork_new->user_defined)
4196 setString(&artwork_new->identifier, "private");
4197 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4201 setString(&artwork_new->identifier, "classic");
4202 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4205 setString(&artwork_new->class_desc,
4206 getLevelClassDescription(artwork_new));
4210 setString(&artwork_new->identifier, artwork_new->subdir);
4213 setString(&artwork_new->name, artwork_new->identifier);
4214 setString(&artwork_new->name_sorting, artwork_new->name);
4217 pushTreeInfo(node_first, artwork_new);
4219 freeSetupFileHash(setup_file_hash);
4221 free(directory_path);
4227 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4228 TreeInfo *node_parent,
4229 char *base_directory, int type)
4231 // ---------- 1st stage: process any artwork set zip files ----------
4233 ProcessZipFilesInDirectory(base_directory, type);
4235 // ---------- 2nd stage: check for artwork set directories ----------
4238 DirectoryEntry *dir_entry;
4239 boolean valid_entry_found = FALSE;
4241 if ((dir = openDirectory(base_directory)) == NULL)
4243 // display error if directory is main "options.graphics_directory" etc.
4244 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4245 Warn("cannot read directory '%s'", base_directory);
4250 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4252 char *directory_name = dir_entry->basename;
4253 char *directory_path = getPath2(base_directory, directory_name);
4255 // skip directory entries for current and parent directory
4256 if (strEqual(directory_name, ".") ||
4257 strEqual(directory_name, ".."))
4259 free(directory_path);
4264 // skip directory entries which are not a directory
4265 if (!dir_entry->is_directory) // not a directory
4267 free(directory_path);
4272 free(directory_path);
4274 // check if this directory contains artwork with or without config file
4275 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4277 directory_name, type);
4280 closeDirectory(dir);
4282 // check if this directory directly contains artwork itself
4283 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4284 base_directory, ".",
4286 if (!valid_entry_found)
4287 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4290 static TreeInfo *getDummyArtworkInfo(int type)
4292 // this is only needed when there is completely no artwork available
4293 TreeInfo *artwork_new = newTreeInfo();
4295 setTreeInfoToDefaults(artwork_new, type);
4297 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4298 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4299 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4301 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4302 setString(&artwork_new->name, UNDEFINED_FILENAME);
4303 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4308 void SetCurrentArtwork(int type)
4310 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4311 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4312 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4313 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4315 // set current artwork to artwork configured in setup menu
4316 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4318 // if not found, set current artwork to default artwork
4319 if (*current_ptr == NULL)
4320 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4322 // if not found, set current artwork to first artwork in tree
4323 if (*current_ptr == NULL)
4324 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4327 void ChangeCurrentArtworkIfNeeded(int type)
4329 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4330 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4332 if (!strEqual(current_identifier, setup_set))
4333 SetCurrentArtwork(type);
4336 void LoadArtworkInfo(void)
4338 LoadArtworkInfoCache();
4340 DrawInitTextHead("Looking for custom artwork");
4342 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4343 options.graphics_directory,
4344 TREE_TYPE_GRAPHICS_DIR);
4345 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4346 getUserGraphicsDir(),
4347 TREE_TYPE_GRAPHICS_DIR);
4349 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4350 options.sounds_directory,
4351 TREE_TYPE_SOUNDS_DIR);
4352 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4354 TREE_TYPE_SOUNDS_DIR);
4356 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4357 options.music_directory,
4358 TREE_TYPE_MUSIC_DIR);
4359 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4361 TREE_TYPE_MUSIC_DIR);
4363 if (artwork.gfx_first == NULL)
4364 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4365 if (artwork.snd_first == NULL)
4366 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4367 if (artwork.mus_first == NULL)
4368 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4370 // before sorting, the first entries will be from the user directory
4371 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4372 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4373 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4375 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4376 artwork.snd_current_identifier = artwork.snd_current->identifier;
4377 artwork.mus_current_identifier = artwork.mus_current->identifier;
4379 #if ENABLE_UNUSED_CODE
4380 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4381 artwork.gfx_current_identifier);
4382 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4383 artwork.snd_current_identifier);
4384 Debug("setup:LoadArtworkInfo", "music set == %s",
4385 artwork.mus_current_identifier);
4388 sortTreeInfo(&artwork.gfx_first);
4389 sortTreeInfo(&artwork.snd_first);
4390 sortTreeInfo(&artwork.mus_first);
4392 #if ENABLE_UNUSED_CODE
4393 dumpTreeInfo(artwork.gfx_first, 0);
4394 dumpTreeInfo(artwork.snd_first, 0);
4395 dumpTreeInfo(artwork.mus_first, 0);
4399 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4401 ArtworkDirTree *artwork_new = newTreeInfo();
4402 char *top_node_name = "standalone artwork";
4404 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4406 artwork_new->level_group = TRUE;
4408 setString(&artwork_new->identifier, top_node_name);
4409 setString(&artwork_new->name, top_node_name);
4410 setString(&artwork_new->name_sorting, top_node_name);
4412 // create node to link back to current custom artwork directory
4413 createParentTreeInfoNode(artwork_new);
4415 // move existing custom artwork tree into newly created sub-tree
4416 artwork_new->node_group->next = *artwork_node;
4418 // change custom artwork tree to contain only newly created node
4419 *artwork_node = artwork_new;
4422 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4423 ArtworkDirTree *node_parent,
4424 LevelDirTree *level_node,
4425 boolean empty_level_set_mode)
4427 int type = (*artwork_node)->type;
4429 // recursively check all level directories for artwork sub-directories
4433 boolean empty_level_set = (level_node->levels == 0);
4435 // check all tree entries for artwork, but skip parent link entries
4436 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4438 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4439 boolean cached = (artwork_new != NULL);
4443 pushTreeInfo(artwork_node, artwork_new);
4447 TreeInfo *topnode_last = *artwork_node;
4448 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4449 ARTWORK_DIRECTORY(type));
4451 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4453 if (topnode_last != *artwork_node) // check for newly added node
4455 artwork_new = *artwork_node;
4457 setString(&artwork_new->identifier, level_node->subdir);
4458 setString(&artwork_new->name, level_node->name);
4459 setString(&artwork_new->name_sorting, level_node->name_sorting);
4461 artwork_new->sort_priority = level_node->sort_priority;
4462 artwork_new->in_user_dir = level_node->in_user_dir;
4464 update_artworkinfo_cache = TRUE;
4470 // insert artwork info (from old cache or filesystem) into new cache
4471 if (artwork_new != NULL)
4472 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4475 DrawInitTextItem(level_node->name);
4477 if (level_node->node_group != NULL)
4479 TreeInfo *artwork_new = newTreeInfo();
4482 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4484 setTreeInfoToDefaults(artwork_new, type);
4486 artwork_new->level_group = TRUE;
4488 setString(&artwork_new->identifier, level_node->subdir);
4490 if (node_parent == NULL) // check for top tree node
4492 char *top_node_name = (empty_level_set_mode ?
4493 "artwork for certain level sets" :
4494 "artwork included in level sets");
4496 setString(&artwork_new->name, top_node_name);
4497 setString(&artwork_new->name_sorting, top_node_name);
4501 setString(&artwork_new->name, level_node->name);
4502 setString(&artwork_new->name_sorting, level_node->name_sorting);
4505 pushTreeInfo(artwork_node, artwork_new);
4507 // create node to link back to current custom artwork directory
4508 createParentTreeInfoNode(artwork_new);
4510 // recursively step into sub-directory and look for more custom artwork
4511 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4512 level_node->node_group,
4513 empty_level_set_mode);
4515 // if sub-tree has no custom artwork at all, remove it
4516 if (artwork_new->node_group->next == NULL)
4517 removeTreeInfo(artwork_node);
4520 level_node = level_node->next;
4524 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4526 // move peviously loaded artwork tree into separate sub-tree
4527 MoveArtworkInfoIntoSubTree(artwork_node);
4529 // load artwork from level sets into separate sub-trees
4530 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4531 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4533 // add top tree node over all sub-trees and set parent links
4534 *artwork_node = addTopTreeInfoNode(*artwork_node);
4537 void LoadLevelArtworkInfo(void)
4539 print_timestamp_init("LoadLevelArtworkInfo");
4541 DrawInitTextHead("Looking for custom level artwork");
4543 print_timestamp_time("DrawTimeText");
4545 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4546 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4547 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4548 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4549 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4550 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4552 SaveArtworkInfoCache();
4554 print_timestamp_time("SaveArtworkInfoCache");
4556 // needed for reloading level artwork not known at ealier stage
4557 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4558 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4559 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4561 print_timestamp_time("getTreeInfoFromIdentifier");
4563 sortTreeInfo(&artwork.gfx_first);
4564 sortTreeInfo(&artwork.snd_first);
4565 sortTreeInfo(&artwork.mus_first);
4567 print_timestamp_time("sortTreeInfo");
4569 #if ENABLE_UNUSED_CODE
4570 dumpTreeInfo(artwork.gfx_first, 0);
4571 dumpTreeInfo(artwork.snd_first, 0);
4572 dumpTreeInfo(artwork.mus_first, 0);
4575 print_timestamp_done("LoadLevelArtworkInfo");
4578 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4579 char *tree_subdir_new, int type)
4581 if (tree_node_old == NULL)
4583 if (type == TREE_TYPE_LEVEL_DIR)
4585 // get level info tree node of personal user level set
4586 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4588 // this may happen if "setup.internal.create_user_levelset" is FALSE
4589 // or if file "levelinfo.conf" is missing in personal user level set
4590 if (tree_node_old == NULL)
4591 tree_node_old = leveldir_first->node_group;
4595 // get artwork info tree node of first artwork set
4596 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4600 if (tree_dir == NULL)
4601 tree_dir = TREE_USERDIR(type);
4603 if (tree_node_old == NULL ||
4605 tree_subdir_new == NULL) // should not happen
4608 int draw_deactivation_mask = GetDrawDeactivationMask();
4610 // override draw deactivation mask (temporarily disable drawing)
4611 SetDrawDeactivationMask(REDRAW_ALL);
4613 if (type == TREE_TYPE_LEVEL_DIR)
4615 // load new level set config and add it next to first user level set
4616 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4617 tree_node_old->node_parent,
4618 tree_dir, tree_subdir_new);
4622 // load new artwork set config and add it next to first artwork set
4623 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4624 tree_node_old->node_parent,
4625 tree_dir, tree_subdir_new, type);
4628 // set draw deactivation mask to previous value
4629 SetDrawDeactivationMask(draw_deactivation_mask);
4631 // get first node of level or artwork info tree
4632 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4634 // get tree info node of newly added level or artwork set
4635 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4638 // if not found, check if added node is level group or artwork group
4639 if (tree_node_new == NULL)
4640 tree_node_new = getTreeInfoFromIdentifierExt(*tree_node_first,
4642 TREE_NODE_TYPE_GROUP);
4644 if (tree_node_new == NULL) // should not happen
4647 // correct top link and parent node link of newly created tree node
4648 tree_node_new->node_top = tree_node_old->node_top;
4649 tree_node_new->node_parent = tree_node_old->node_parent;
4651 // sort tree info to adjust position of newly added tree set
4652 sortTreeInfo(tree_node_first);
4657 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4658 char *tree_subdir_new, int type)
4660 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4661 Fail("internal tree info structure corrupted -- aborting");
4664 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4666 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4669 char *getArtworkIdentifierForUserLevelSet(int type)
4671 char *classic_artwork_set = getClassicArtworkSet(type);
4673 // check for custom artwork configured in "levelinfo.conf"
4674 char *leveldir_artwork_set =
4675 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4676 boolean has_leveldir_artwork_set =
4677 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4678 classic_artwork_set));
4680 // check for custom artwork in sub-directory "graphics" etc.
4681 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4682 char *leveldir_identifier = leveldir_current->identifier;
4683 boolean has_artwork_subdir =
4684 (getTreeInfoFromIdentifier(artwork_first_node,
4685 leveldir_identifier) != NULL);
4687 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4688 has_artwork_subdir ? leveldir_identifier :
4689 classic_artwork_set);
4692 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4694 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4695 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4696 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4700 ti = getTreeInfoFromIdentifier(artwork_first_node,
4701 ARTWORK_DEFAULT_SUBDIR(type));
4703 Fail("cannot find default graphics -- should not happen");
4709 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4711 char *graphics_set =
4712 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4714 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4716 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4718 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4719 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4720 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4723 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4724 char *level_author, int num_levels)
4726 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4727 char *filename_tmp = getStringCat2(filename, ".tmp");
4729 FILE *file_tmp = NULL;
4730 char line[MAX_LINE_LEN];
4731 boolean success = FALSE;
4732 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4734 // update values in level directory tree
4736 if (level_name != NULL)
4737 setString(&leveldir->name, level_name);
4739 if (level_author != NULL)
4740 setString(&leveldir->author, level_author);
4742 if (num_levels != -1)
4743 leveldir->levels = num_levels;
4745 // update values that depend on other values
4747 setString(&leveldir->name_sorting, leveldir->name);
4749 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4751 // sort order of level sets may have changed
4752 sortTreeInfo(&leveldir_first);
4754 if ((file = fopen(filename, MODE_READ)) &&
4755 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4757 while (fgets(line, MAX_LINE_LEN, file))
4759 if (strPrefix(line, "name:") && level_name != NULL)
4760 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4761 else if (strPrefix(line, "author:") && level_author != NULL)
4762 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4763 else if (strPrefix(line, "levels:") && num_levels != -1)
4764 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4766 fputs(line, file_tmp);
4779 success = (rename(filename_tmp, filename) == 0);
4787 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4788 char *level_author, int num_levels,
4789 boolean use_artwork_set)
4791 LevelDirTree *level_info;
4796 // create user level sub-directory, if needed
4797 createDirectory(getUserLevelDir(level_subdir), "user level");
4799 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4801 if (!(file = fopen(filename, MODE_WRITE)))
4803 Warn("cannot write level info file '%s'", filename);
4810 level_info = newTreeInfo();
4812 // always start with reliable default values
4813 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4815 setString(&level_info->name, level_name);
4816 setString(&level_info->author, level_author);
4817 level_info->levels = num_levels;
4818 level_info->first_level = 1;
4819 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4820 level_info->readonly = FALSE;
4822 if (use_artwork_set)
4824 level_info->graphics_set =
4825 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4826 level_info->sounds_set =
4827 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4828 level_info->music_set =
4829 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4832 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4834 fprintFileHeader(file, LEVELINFO_FILENAME);
4837 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4839 if (i == LEVELINFO_TOKEN_NAME ||
4840 i == LEVELINFO_TOKEN_AUTHOR ||
4841 i == LEVELINFO_TOKEN_LEVELS ||
4842 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4843 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4844 i == LEVELINFO_TOKEN_READONLY ||
4845 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4846 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4847 i == LEVELINFO_TOKEN_MUSIC_SET)))
4848 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4850 // just to make things nicer :)
4851 if (i == LEVELINFO_TOKEN_AUTHOR ||
4852 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4853 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4854 fprintf(file, "\n");
4857 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4861 SetFilePermissions(filename, PERMS_PRIVATE);
4863 freeTreeInfo(level_info);
4869 static void SaveUserLevelInfo(void)
4871 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4874 char *getSetupValue(int type, void *value)
4876 static char value_string[MAX_LINE_LEN];
4884 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4888 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4891 case TYPE_SWITCH_3_STATES:
4892 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4893 *(int *)value == STATE_ASK ? "ask" :
4894 *(int *)value == STATE_FALSE ? "off" : "on"));
4898 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4901 case TYPE_YES_NO_AUTO:
4902 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4903 *(int *)value == STATE_FALSE ? "no" : "yes"));
4906 case TYPE_YES_NO_ASK:
4907 strcpy(value_string, (*(int *)value == STATE_ASK ? "ask" :
4908 *(int *)value == STATE_FALSE ? "no" : "yes"));
4912 strcpy(value_string, (*(boolean *)value ? "new" : "old"));
4916 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4920 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4924 sprintf(value_string, "%d", *(int *)value);
4928 if (*(char **)value == NULL)
4931 strcpy(value_string, *(char **)value);
4935 sprintf(value_string, "player_%d", *(int *)value + 1);
4939 value_string[0] = '\0';
4943 if (type & TYPE_GHOSTED)
4944 strcpy(value_string, "n/a");
4946 return value_string;
4949 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4953 static char token_string[MAX_LINE_LEN];
4954 int token_type = token_info[token_nr].type;
4955 void *setup_value = token_info[token_nr].value;
4956 char *token_text = token_info[token_nr].text;
4957 char *value_string = getSetupValue(token_type, setup_value);
4959 // build complete token string
4960 sprintf(token_string, "%s%s", prefix, token_text);
4962 // build setup entry line
4963 line = getFormattedSetupEntry(token_string, value_string);
4965 if (token_type == TYPE_KEY_X11)
4967 Key key = *(Key *)setup_value;
4968 char *keyname = getKeyNameFromKey(key);
4970 // add comment, if useful
4971 if (!strEqual(keyname, "(undefined)") &&
4972 !strEqual(keyname, "(unknown)"))
4974 // add at least one whitespace
4976 for (i = strlen(line); i < token_comment_position; i++)
4980 strcat(line, keyname);
4987 static void InitLastPlayedLevels_ParentNode(void)
4989 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4990 LevelDirTree *leveldir_new = NULL;
4992 // check if parent node for last played levels already exists
4993 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4996 leveldir_new = newTreeInfo();
4998 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
5000 leveldir_new->level_group = TRUE;
5001 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
5003 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
5004 setString(&leveldir_new->name, "<< (last played level sets)");
5005 setString(&leveldir_new->name_sorting, leveldir_new->name);
5007 pushTreeInfo(leveldir_top, leveldir_new);
5009 // create node to link back to current level directory
5010 createParentTreeInfoNode(leveldir_new);
5013 void UpdateLastPlayedLevels_TreeInfo(void)
5015 char **last_level_series = setup.level_setup.last_level_series;
5016 LevelDirTree *leveldir_last;
5017 TreeInfo **node_new = NULL;
5020 if (last_level_series[0] == NULL)
5023 InitLastPlayedLevels_ParentNode();
5025 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
5026 TOKEN_STR_LAST_LEVEL_SERIES,
5027 TREE_NODE_TYPE_GROUP);
5028 if (leveldir_last == NULL)
5031 node_new = &leveldir_last->node_group->next;
5033 freeTreeInfo(*node_new);
5037 for (i = 0; last_level_series[i] != NULL; i++)
5039 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
5040 last_level_series[i]);
5041 if (node_last == NULL)
5044 *node_new = getTreeInfoCopy(node_last); // copy complete node
5046 (*node_new)->node_top = &leveldir_first; // correct top node link
5047 (*node_new)->node_parent = leveldir_last; // correct parent node link
5049 (*node_new)->is_copy = TRUE; // mark entry as node copy
5051 (*node_new)->node_group = NULL;
5052 (*node_new)->next = NULL;
5054 (*node_new)->cl_first = -1; // force setting tree cursor
5056 node_new = &((*node_new)->next);
5060 static void UpdateLastPlayedLevels_List(void)
5062 char **last_level_series = setup.level_setup.last_level_series;
5063 int pos = MAX_LEVELDIR_HISTORY - 1;
5066 // search for potentially already existing entry in list of level sets
5067 for (i = 0; last_level_series[i] != NULL; i++)
5068 if (strEqual(last_level_series[i], leveldir_current->identifier))
5071 // move list of level sets one entry down (using potentially free entry)
5072 for (i = pos; i > 0; i--)
5073 setString(&last_level_series[i], last_level_series[i - 1]);
5075 // put last played level set at top position
5076 setString(&last_level_series[0], leveldir_current->identifier);
5079 #define LAST_PLAYED_MODE_SET 1
5080 #define LAST_PLAYED_MODE_SET_FORCED 2
5081 #define LAST_PLAYED_MODE_GET 3
5083 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5085 static char *identifier = NULL;
5087 if (mode == LAST_PLAYED_MODE_SET)
5089 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5091 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5093 setString(&identifier, (node ? node->identifier : NULL));
5095 else if (mode == LAST_PLAYED_MODE_GET)
5097 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5099 TREE_NODE_TYPE_COPY);
5100 return (node_new != NULL ? node_new : node);
5103 return NULL; // not used
5106 void StoreLastPlayedLevels(TreeInfo *node)
5108 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5111 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5113 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5116 void RestoreLastPlayedLevels(TreeInfo **node)
5118 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5121 boolean CheckLastPlayedLevels(void)
5123 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5126 void LoadLevelSetup_LastSeries(void)
5128 // --------------------------------------------------------------------------
5129 // ~/.<program>/levelsetup.conf
5130 // --------------------------------------------------------------------------
5132 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5133 SetupFileHash *level_setup_hash = NULL;
5137 // always start with reliable default values
5138 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5140 // start with empty history of last played level sets
5141 setString(&setup.level_setup.last_level_series[0], NULL);
5143 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5145 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5147 if (leveldir_current == NULL)
5148 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5151 if ((level_setup_hash = loadSetupFileHash(filename)))
5153 char *last_level_series =
5154 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5156 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5158 if (leveldir_current == NULL)
5159 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5161 char *last_played_menu_used =
5162 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5164 // store if last level set was selected from "last played" menu
5165 if (strEqual(last_played_menu_used, "true"))
5166 ForcedStoreLastPlayedLevels(leveldir_current);
5168 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5170 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5171 LevelDirTree *leveldir_last;
5173 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5175 last_level_series = getHashEntry(level_setup_hash, token);
5177 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5179 if (leveldir_last != NULL)
5180 setString(&setup.level_setup.last_level_series[pos++],
5184 setString(&setup.level_setup.last_level_series[pos], NULL);
5186 freeSetupFileHash(level_setup_hash);
5190 Debug("setup", "using default setup values");
5196 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5198 // --------------------------------------------------------------------------
5199 // ~/.<program>/levelsetup.conf
5200 // --------------------------------------------------------------------------
5202 // check if the current level directory structure is available at this point
5203 if (leveldir_current == NULL)
5206 char **last_level_series = setup.level_setup.last_level_series;
5207 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5211 InitUserDataDirectory();
5213 UpdateLastPlayedLevels_List();
5215 if (!(file = fopen(filename, MODE_WRITE)))
5217 Warn("cannot write setup file '%s'", filename);
5224 fprintFileHeader(file, LEVELSETUP_FILENAME);
5226 if (deactivate_last_level_series)
5227 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5229 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5230 leveldir_current->identifier));
5232 // store if last level set was selected from "last played" menu
5233 boolean last_played_menu_used = CheckLastPlayedLevels();
5234 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5236 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5239 for (i = 0; last_level_series[i] != NULL; i++)
5241 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5243 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5245 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5250 SetFilePermissions(filename, PERMS_PRIVATE);
5255 void SaveLevelSetup_LastSeries(void)
5257 SaveLevelSetup_LastSeries_Ext(FALSE);
5260 void SaveLevelSetup_LastSeries_Deactivate(void)
5262 SaveLevelSetup_LastSeries_Ext(TRUE);
5265 static void checkSeriesInfo(void)
5267 static char *level_directory = NULL;
5270 DirectoryEntry *dir_entry;
5273 checked_free(level_directory);
5275 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5277 level_directory = getPath2((leveldir_current->in_user_dir ?
5278 getUserLevelDir(NULL) :
5279 options.level_directory),
5280 leveldir_current->fullpath);
5282 if ((dir = openDirectory(level_directory)) == NULL)
5284 Warn("cannot read level directory '%s'", level_directory);
5290 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5292 if (strlen(dir_entry->basename) > 4 &&
5293 dir_entry->basename[3] == '.' &&
5294 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5296 char levelnum_str[4];
5299 strncpy(levelnum_str, dir_entry->basename, 3);
5300 levelnum_str[3] = '\0';
5302 levelnum_value = atoi(levelnum_str);
5304 if (levelnum_value < leveldir_current->first_level)
5306 Warn("additional level %d found", levelnum_value);
5308 leveldir_current->first_level = levelnum_value;
5310 else if (levelnum_value > leveldir_current->last_level)
5312 Warn("additional level %d found", levelnum_value);
5314 leveldir_current->last_level = levelnum_value;
5320 closeDirectory(dir);
5323 void LoadLevelSetup_SeriesInfo(void)
5326 SetupFileHash *level_setup_hash = NULL;
5327 char *level_subdir = leveldir_current->subdir;
5330 // always start with reliable default values
5331 level_nr = leveldir_current->first_level;
5333 for (i = 0; i < MAX_LEVELS; i++)
5335 LevelStats_setPlayed(i, 0);
5336 LevelStats_setSolved(i, 0);
5341 // --------------------------------------------------------------------------
5342 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5343 // --------------------------------------------------------------------------
5345 level_subdir = leveldir_current->subdir;
5347 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5349 if ((level_setup_hash = loadSetupFileHash(filename)))
5353 // get last played level in this level set
5355 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5359 level_nr = atoi(token_value);
5361 if (level_nr < leveldir_current->first_level)
5362 level_nr = leveldir_current->first_level;
5363 if (level_nr > leveldir_current->last_level)
5364 level_nr = leveldir_current->last_level;
5367 // get handicap level in this level set
5369 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5373 int level_nr = atoi(token_value);
5375 if (level_nr < leveldir_current->first_level)
5376 level_nr = leveldir_current->first_level;
5377 if (level_nr > leveldir_current->last_level + 1)
5378 level_nr = leveldir_current->last_level;
5380 if (leveldir_current->user_defined || !leveldir_current->handicap)
5381 level_nr = leveldir_current->last_level;
5383 leveldir_current->handicap_level = level_nr;
5386 // get number of played and solved levels in this level set
5388 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5390 char *token = HASH_ITERATION_TOKEN(itr);
5391 char *value = HASH_ITERATION_VALUE(itr);
5393 if (strlen(token) == 3 &&
5394 token[0] >= '0' && token[0] <= '9' &&
5395 token[1] >= '0' && token[1] <= '9' &&
5396 token[2] >= '0' && token[2] <= '9')
5398 int level_nr = atoi(token);
5401 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5403 value = strchr(value, ' ');
5406 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5409 END_HASH_ITERATION(hash, itr)
5411 freeSetupFileHash(level_setup_hash);
5415 Debug("setup", "using default setup values");
5421 void SaveLevelSetup_SeriesInfo(void)
5424 char *level_subdir = leveldir_current->subdir;
5425 char *level_nr_str = int2str(level_nr, 0);
5426 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5430 // --------------------------------------------------------------------------
5431 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5432 // --------------------------------------------------------------------------
5434 InitLevelSetupDirectory(level_subdir);
5436 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5438 if (!(file = fopen(filename, MODE_WRITE)))
5440 Warn("cannot write setup file '%s'", filename);
5447 fprintFileHeader(file, LEVELSETUP_FILENAME);
5449 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5451 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5452 handicap_level_str));
5454 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5457 if (LevelStats_getPlayed(i) > 0 ||
5458 LevelStats_getSolved(i) > 0)
5463 sprintf(token, "%03d", i);
5464 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5466 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5472 SetFilePermissions(filename, PERMS_PRIVATE);
5477 int LevelStats_getPlayed(int nr)
5479 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5482 int LevelStats_getSolved(int nr)
5484 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5487 void LevelStats_setPlayed(int nr, int value)
5489 if (nr >= 0 && nr < MAX_LEVELS)
5490 level_stats[nr].played = value;
5493 void LevelStats_setSolved(int nr, int value)
5495 if (nr >= 0 && nr < MAX_LEVELS)
5496 level_stats[nr].solved = value;
5499 void LevelStats_incPlayed(int nr)
5501 if (nr >= 0 && nr < MAX_LEVELS)
5502 level_stats[nr].played++;
5505 void LevelStats_incSolved(int nr)
5507 if (nr >= 0 && nr < MAX_LEVELS)
5508 level_stats[nr].solved++;
5511 void LoadUserSetup(void)
5513 // --------------------------------------------------------------------------
5514 // ~/.<program>/usersetup.conf
5515 // --------------------------------------------------------------------------
5517 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5518 SetupFileHash *user_setup_hash = NULL;
5520 // always start with reliable default values
5523 if ((user_setup_hash = loadSetupFileHash(filename)))
5527 // get last selected user number
5528 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5531 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5533 freeSetupFileHash(user_setup_hash);
5537 Debug("setup", "using default setup values");
5543 void SaveUserSetup(void)
5545 // --------------------------------------------------------------------------
5546 // ~/.<program>/usersetup.conf
5547 // --------------------------------------------------------------------------
5549 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5552 InitMainUserDataDirectory();
5554 if (!(file = fopen(filename, MODE_WRITE)))
5556 Warn("cannot write setup file '%s'", filename);
5563 fprintFileHeader(file, USERSETUP_FILENAME);
5565 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5569 SetFilePermissions(filename, PERMS_PRIVATE);