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 static char *getHelpFilename(char *basename)
828 static char *filename = NULL;
830 checked_free(filename);
832 // 1st try: look for help filename in current level set directory
833 filename = getPath2(getCurrentLevelDir(), basename);
834 if (fileExists(filename))
839 // 2nd try: look for help filename in configured graphics set directory
840 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
841 if (fileExists(filename))
846 // 3rd try: look for help filename in path from current to topmost level set directory
847 filename = getStringCopy(getFilenameFromCurrentLevelDirUpward(basename));
848 if (fileExists(filename))
854 char *getHelpAnimFilename(void)
856 return getHelpFilename(HELPANIM_FILENAME);
859 char *getHelpTextFilename(void)
861 return getHelpFilename(HELPTEXT_FILENAME);
864 static char *getLevelSetInfoBasename(int nr)
866 static char basename[32];
868 sprintf(basename, "levelset_%d.txt", nr + 1);
873 char *getLevelSetInfoFilename(int nr)
875 char *basename = getLevelSetInfoBasename(nr);
876 static char *info_subdir = NULL;
877 static char *filename = NULL;
879 if (info_subdir == NULL)
880 info_subdir = getPath2(DOCS_DIRECTORY, LEVELSET_INFO_DIRECTORY);
882 checked_free(filename);
884 // look for level set info file the current level set directory
885 filename = getPath3(getCurrentLevelDir(), info_subdir, basename);
886 if (fileExists(filename))
906 for (i = 0; basenames[i] != NULL; i++)
908 checked_free(filename);
909 filename = getPath2(getCurrentLevelDir(), basenames[i]);
911 if (fileExists(filename))
918 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
920 static char basename[32];
922 sprintf(basename, "%s_%d.txt",
923 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
928 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
930 static char *filename = NULL;
932 boolean skip_setup_artwork = FALSE;
934 checked_free(filename);
936 basename = getLevelSetTitleMessageBasename(nr, initial);
938 if (!gfx.override_level_graphics)
940 // 1st try: look for special artwork in current level series directory
941 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
942 if (fileExists(filename))
947 // 2nd try: look for message file in current level set directory
948 filename = getPath2(getCurrentLevelDir(), basename);
949 if (fileExists(filename))
954 // check if there is special artwork configured in level series config
955 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
957 // 3rd try: look for special artwork configured in level series config
958 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
959 if (fileExists(filename))
964 // take missing artwork configured in level set config from default
965 skip_setup_artwork = TRUE;
969 if (!skip_setup_artwork)
971 // 4th try: look for special artwork in configured artwork directory
972 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
973 if (fileExists(filename))
979 // 5th try: look for default artwork in new default artwork directory
980 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
981 if (fileExists(filename))
986 // 6th try: look for default artwork in old default artwork directory
987 filename = getPath2(options.graphics_directory, basename);
988 if (fileExists(filename))
991 return NULL; // cannot find specified artwork file anywhere
994 static char *getCreditsBasename(int nr)
996 static char basename[32];
998 sprintf(basename, "credits_%d.txt", nr + 1);
1003 char *getCreditsFilename(int nr, boolean global)
1005 char *basename = getCreditsBasename(nr);
1006 char *basepath = NULL;
1007 static char *credits_subdir = NULL;
1008 static char *filename = NULL;
1010 if (credits_subdir == NULL)
1011 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
1013 checked_free(filename);
1015 // look for credits file in the game's base or current level set directory
1016 basepath = (global ? options.base_directory : getCurrentLevelDir());
1018 filename = getPath3(basepath, credits_subdir, basename);
1019 if (fileExists(filename))
1022 return NULL; // cannot find credits file
1025 static char *getProgramInfoBasename(int nr)
1027 static char basename[32];
1029 sprintf(basename, "program_%d.txt", nr + 1);
1034 char *getProgramInfoFilename(int nr)
1036 char *basename = getProgramInfoBasename(nr);
1037 static char *info_subdir = NULL;
1038 static char *filename = NULL;
1040 if (info_subdir == NULL)
1041 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
1043 checked_free(filename);
1045 // look for program info file in the game's base directory
1046 filename = getPath3(options.base_directory, info_subdir, basename);
1047 if (fileExists(filename))
1050 return NULL; // cannot find program info file
1053 static char *getCorrectedArtworkBasename(char *basename)
1058 char *getCustomImageFilename(char *basename)
1060 static char *filename = NULL;
1061 boolean skip_setup_artwork = FALSE;
1063 checked_free(filename);
1065 basename = getCorrectedArtworkBasename(basename);
1067 if (!gfx.override_level_graphics)
1069 // 1st try: look for special artwork in current level series directory
1070 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1071 if (fileExists(filename))
1076 // check if there is special artwork configured in level series config
1077 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1079 // 2nd try: look for special artwork configured in level series config
1080 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1081 if (fileExists(filename))
1086 // take missing artwork configured in level set config from default
1087 skip_setup_artwork = TRUE;
1091 if (!skip_setup_artwork)
1093 // 3rd try: look for special artwork in configured artwork directory
1094 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1095 if (fileExists(filename))
1101 // 4th try: look for default artwork in new default artwork directory
1102 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1103 if (fileExists(filename))
1108 // 5th try: look for default artwork in old default artwork directory
1109 filename = getImg2(options.graphics_directory, basename);
1110 if (fileExists(filename))
1113 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1117 WarnUsingFallback(basename);
1119 // 6th try: look for fallback artwork in old default artwork directory
1120 // (needed to prevent errors when trying to access unused artwork files)
1121 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1122 if (fileExists(filename))
1126 return NULL; // cannot find specified artwork file anywhere
1129 char *getCustomSoundFilename(char *basename)
1131 static char *filename = NULL;
1132 boolean skip_setup_artwork = FALSE;
1134 checked_free(filename);
1136 basename = getCorrectedArtworkBasename(basename);
1138 if (!gfx.override_level_sounds)
1140 // 1st try: look for special artwork in current level series directory
1141 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1142 if (fileExists(filename))
1147 // check if there is special artwork configured in level series config
1148 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1150 // 2nd try: look for special artwork configured in level series config
1151 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1152 if (fileExists(filename))
1157 // take missing artwork configured in level set config from default
1158 skip_setup_artwork = TRUE;
1162 if (!skip_setup_artwork)
1164 // 3rd try: look for special artwork in configured artwork directory
1165 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1166 if (fileExists(filename))
1172 // 4th try: look for default artwork in new default artwork directory
1173 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1174 if (fileExists(filename))
1179 // 5th try: look for default artwork in old default artwork directory
1180 filename = getPath2(options.sounds_directory, basename);
1181 if (fileExists(filename))
1184 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1188 WarnUsingFallback(basename);
1190 // 6th try: look for fallback artwork in old default artwork directory
1191 // (needed to prevent errors when trying to access unused artwork files)
1192 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1193 if (fileExists(filename))
1197 return NULL; // cannot find specified artwork file anywhere
1200 char *getCustomMusicFilename(char *basename)
1202 static char *filename = NULL;
1203 boolean skip_setup_artwork = FALSE;
1205 checked_free(filename);
1207 basename = getCorrectedArtworkBasename(basename);
1209 if (!gfx.override_level_music)
1211 // 1st try: look for special artwork in current level series directory
1212 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1213 if (fileExists(filename))
1218 // check if there is special artwork configured in level series config
1219 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1221 // 2nd try: look for special artwork configured in level series config
1222 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1223 if (fileExists(filename))
1228 // take missing artwork configured in level set config from default
1229 skip_setup_artwork = TRUE;
1233 if (!skip_setup_artwork)
1235 // 3rd try: look for special artwork in configured artwork directory
1236 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1237 if (fileExists(filename))
1243 // 4th try: look for default artwork in new default artwork directory
1244 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1245 if (fileExists(filename))
1250 // 5th try: look for default artwork in old default artwork directory
1251 filename = getPath2(options.music_directory, basename);
1252 if (fileExists(filename))
1255 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1259 WarnUsingFallback(basename);
1261 // 6th try: look for fallback artwork in old default artwork directory
1262 // (needed to prevent errors when trying to access unused artwork files)
1263 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1264 if (fileExists(filename))
1268 return NULL; // cannot find specified artwork file anywhere
1271 char *getCustomArtworkFilename(char *basename, int type)
1273 if (type == ARTWORK_TYPE_GRAPHICS)
1274 return getCustomImageFilename(basename);
1275 else if (type == ARTWORK_TYPE_SOUNDS)
1276 return getCustomSoundFilename(basename);
1277 else if (type == ARTWORK_TYPE_MUSIC)
1278 return getCustomMusicFilename(basename);
1280 return UNDEFINED_FILENAME;
1283 char *getCustomArtworkConfigFilename(int type)
1285 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1288 char *getCustomArtworkLevelConfigFilename(int type)
1290 static char *filename = NULL;
1292 checked_free(filename);
1294 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1299 static boolean directoryExists_CheckMusic(char *directory, boolean check_music)
1301 if (!directoryExists(directory))
1308 DirectoryEntry *dir_entry;
1309 int num_music = getMusicListSize();
1310 boolean music_found = FALSE;
1312 if ((dir = openDirectory(directory)) == NULL)
1315 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
1317 char *basename = dir_entry->basename;
1318 boolean music_already_used = FALSE;
1321 // skip all music files that are configured in music config file
1322 for (i = 0; i < num_music; i++)
1324 struct FileInfo *music = getMusicListEntry(i);
1326 if (strEqual(basename, music->filename))
1328 music_already_used = TRUE;
1334 if (music_already_used)
1337 if (FileIsMusic(dir_entry->filename))
1345 closeDirectory(dir);
1350 static char *getCustomMusicDirectoryExt(boolean check_music)
1352 static char *directory = NULL;
1353 boolean skip_setup_artwork = FALSE;
1355 checked_free(directory);
1357 if (!gfx.override_level_music)
1359 // 1st try: look for special artwork in current level series directory
1360 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1361 if (directoryExists_CheckMusic(directory, check_music))
1366 // check if there is special artwork configured in level series config
1367 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1369 // 2nd try: look for special artwork configured in level series config
1370 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1372 // directory also valid if no unconfigured music found (no game music)
1373 if (directoryExists_CheckMusic(directory, FALSE))
1378 // take missing artwork configured in level set config from default
1379 skip_setup_artwork = TRUE;
1383 if (!skip_setup_artwork)
1385 // 3rd try: look for special artwork in configured artwork directory
1386 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1388 // directory also valid if no unconfigured music found (no game music)
1389 if (directoryExists_CheckMusic(directory, FALSE))
1395 // 4th try: look for default artwork in new default artwork directory
1396 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1397 if (directoryExists_CheckMusic(directory, check_music))
1402 // 5th try: look for default artwork in old default artwork directory
1403 directory = getStringCopy(options.music_directory);
1404 if (directoryExists_CheckMusic(directory, check_music))
1407 return NULL; // cannot find specified artwork file anywhere
1410 char *getCustomMusicDirectory(void)
1412 return getCustomMusicDirectoryExt(FALSE);
1415 char *getCustomMusicDirectory_NoConf(void)
1417 return getCustomMusicDirectoryExt(TRUE);
1420 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1422 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1424 touchFile(filename);
1426 checked_free(filename);
1429 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1431 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1435 checked_free(filename);
1438 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1440 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1441 boolean success = fileExists(filename);
1443 checked_free(filename);
1448 void InitMissingFileHash(void)
1450 if (missing_file_hash == NULL)
1451 freeSetupFileHash(missing_file_hash);
1453 missing_file_hash = newSetupFileHash();
1456 void InitTapeDirectory(char *level_subdir)
1458 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1460 createDirectory(getUserGameDataDir(), "user data");
1461 createDirectory(getTapeDir(NULL), "main tape");
1462 createDirectory(getTapeDir(level_subdir), "level tape");
1465 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1468 void InitScoreDirectory(char *level_subdir)
1470 createDirectory(getMainUserGameDataDir(), "main user data");
1471 createDirectory(getScoreDir(NULL), "main score");
1472 createDirectory(getScoreDir(level_subdir), "level score");
1475 void InitScoreCacheDirectory(char *level_subdir)
1477 createDirectory(getMainUserGameDataDir(), "main user data");
1478 createDirectory(getCacheDir(), "cache data");
1479 createDirectory(getScoreCacheDir(NULL), "main score");
1480 createDirectory(getScoreCacheDir(level_subdir), "level score");
1483 void InitScoreTapeDirectory(char *level_subdir, int nr)
1485 InitScoreDirectory(level_subdir);
1487 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1490 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1492 InitScoreCacheDirectory(level_subdir);
1494 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1497 static void SaveUserLevelInfo(void);
1499 void InitUserLevelDirectory(char *level_subdir)
1501 if (!directoryExists(getUserLevelDir(level_subdir)))
1503 createDirectory(getMainUserGameDataDir(), "main user data");
1504 createDirectory(getUserLevelDir(NULL), "main user level");
1506 if (setup.internal.create_user_levelset)
1508 createDirectory(getUserLevelDir(level_subdir), "user level");
1510 SaveUserLevelInfo();
1515 void InitNetworkLevelDirectory(char *level_subdir)
1517 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1519 createDirectory(getMainUserGameDataDir(), "main user data");
1520 createDirectory(getNetworkDir(), "network data");
1521 createDirectory(getNetworkLevelDir(NULL), "main network level");
1522 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1526 void InitLevelSetupDirectory(char *level_subdir)
1528 createDirectory(getUserGameDataDir(), "user data");
1529 createDirectory(getLevelSetupDir(NULL), "main level setup");
1530 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1533 static void InitCacheDirectory(void)
1535 createDirectory(getMainUserGameDataDir(), "main user data");
1536 createDirectory(getCacheDir(), "cache data");
1540 // ----------------------------------------------------------------------------
1541 // some functions to handle lists of level and artwork directories
1542 // ----------------------------------------------------------------------------
1544 TreeInfo *newTreeInfo(void)
1546 return checked_calloc(sizeof(TreeInfo));
1549 TreeInfo *newTreeInfo_setDefaults(int type)
1551 TreeInfo *ti = newTreeInfo();
1553 setTreeInfoToDefaults(ti, type);
1558 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1560 node_new->next = *node_first;
1561 *node_first = node_new;
1564 void removeTreeInfo(TreeInfo **node_first)
1566 TreeInfo *node_old = *node_first;
1568 *node_first = node_old->next;
1569 node_old->next = NULL;
1571 freeTreeInfo(node_old);
1574 int numTreeInfo(TreeInfo *node)
1587 boolean validLevelSeries(TreeInfo *node)
1589 // in a number of cases, tree node is no valid level set
1590 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1596 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1598 if (validLevelSeries(node))
1600 else if (node->is_copy)
1601 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1603 return getFirstValidTreeInfoEntry(default_node);
1606 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1611 if (node->node_group) // enter node group (step down into tree)
1612 return getFirstValidTreeInfoEntry(node->node_group);
1614 if (node->parent_link) // skip first node (back link) of node group
1615 get_next_node = TRUE;
1617 if (!get_next_node) // get current regular tree node
1620 // get next regular tree node, or step up until one is found
1621 while (node->next == NULL && node->node_parent != NULL)
1622 node = node->node_parent;
1624 return getFirstValidTreeInfoEntry(node->next);
1627 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1629 return getValidTreeInfoEntryExt(node, FALSE);
1632 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1634 return getValidTreeInfoEntryExt(node, TRUE);
1637 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1642 if (node->node_parent == NULL) // top level group
1643 return *node->node_top;
1644 else // sub level group
1645 return node->node_parent->node_group;
1648 int numTreeInfoInGroup(TreeInfo *node)
1650 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1653 int getPosFromTreeInfo(TreeInfo *node)
1655 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1660 if (node_cmp == node)
1664 node_cmp = node_cmp->next;
1670 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1672 TreeInfo *node_default = node;
1684 return node_default;
1687 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1688 int node_type_wanted)
1690 if (identifier == NULL)
1695 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1696 strEqual(identifier, node->identifier))
1699 if (node->node_group)
1701 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1714 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1716 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1719 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1720 TreeInfo *node, boolean skip_sets_without_levels)
1727 if (!node->parent_link && !node->level_group &&
1728 skip_sets_without_levels && node->levels == 0)
1729 return cloneTreeNode(node_top, node_parent, node->next,
1730 skip_sets_without_levels);
1732 node_new = getTreeInfoCopy(node); // copy complete node
1734 node_new->node_top = node_top; // correct top node link
1735 node_new->node_parent = node_parent; // correct parent node link
1737 if (node->level_group)
1738 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1739 skip_sets_without_levels);
1741 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1742 skip_sets_without_levels);
1747 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1749 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1751 *ti_new = ti_cloned;
1754 static boolean adjustTreeArtworkForEMC(char **artwork_set_1,
1755 char **artwork_set_2,
1756 char **artwork_set, boolean prefer_2)
1758 // do nothing if neither special artwork set 1 nor 2 are defined
1759 if (!*artwork_set_1 && !*artwork_set_2)
1762 boolean want_1 = (prefer_2 == FALSE);
1763 boolean want_2 = (prefer_2 == TRUE);
1764 boolean has_only_1 = (!*artwork_set && !*artwork_set_2);
1765 boolean has_only_2 = (!*artwork_set && !*artwork_set_1);
1766 char *artwork_set_new = NULL;
1768 // replace missing special artwork 1 or 2 with (optional) standard artwork
1770 if (!*artwork_set_1)
1771 setString(artwork_set_1, *artwork_set);
1773 if (!*artwork_set_2)
1774 setString(artwork_set_2, *artwork_set);
1776 // set standard artwork to either special artwork 1 or 2, as requested
1778 if (*artwork_set_1 && (want_1 || has_only_1))
1779 artwork_set_new = *artwork_set_1;
1781 if (*artwork_set_2 && (want_2 || has_only_2))
1782 artwork_set_new = *artwork_set_2;
1784 if (artwork_set_new && !strEqual(*artwork_set, artwork_set_new))
1786 setString(artwork_set, artwork_set_new);
1794 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1796 boolean settings_changed = FALSE;
1800 settings_changed |= adjustTreeArtworkForEMC(&node->graphics_set_ecs,
1801 &node->graphics_set_aga,
1802 &node->graphics_set,
1803 setup.prefer_aga_graphics);
1804 if (node->node_group != NULL)
1805 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1810 return settings_changed;
1813 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1815 boolean settings_changed = FALSE;
1819 settings_changed |= adjustTreeArtworkForEMC(&node->sounds_set_default,
1820 &node->sounds_set_lowpass,
1822 setup.prefer_lowpass_sounds);
1823 if (node->node_group != NULL)
1824 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1829 return settings_changed;
1832 int dumpTreeInfo(TreeInfo *node, int depth)
1834 char bullet_list[] = { '-', '*', 'o' };
1835 int num_leaf_nodes = 0;
1839 Debug("tree", "Dumping TreeInfo:");
1843 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1845 for (i = 0; i < depth * 2; i++)
1846 DebugContinued("", " ");
1848 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1849 bullet, node->name, node->identifier,
1850 (node->node_parent ? node->node_parent->identifier : "-"),
1851 (node->node_group ? "[GROUP]" :
1852 node->is_copy ? "[COPY]" : ""));
1854 if (!node->node_group && !node->parent_link)
1858 // use for dumping artwork info tree
1859 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1860 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1863 if (node->node_group != NULL)
1864 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1870 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1872 return num_leaf_nodes;
1875 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1876 int (*compare_function)(const void *,
1879 int num_nodes = numTreeInfo(*node_first);
1880 TreeInfo **sort_array;
1881 TreeInfo *node = *node_first;
1887 // allocate array for sorting structure pointers
1888 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1890 // writing structure pointers to sorting array
1891 while (i < num_nodes && node) // double boundary check...
1893 sort_array[i] = node;
1899 // sorting the structure pointers in the sorting array
1900 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1903 // update the linkage of list elements with the sorted node array
1904 for (i = 0; i < num_nodes - 1; i++)
1905 sort_array[i]->next = sort_array[i + 1];
1906 sort_array[num_nodes - 1]->next = NULL;
1908 // update the linkage of the main list anchor pointer
1909 *node_first = sort_array[0];
1913 // now recursively sort the level group structures
1917 if (node->node_group != NULL)
1918 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1924 void sortTreeInfo(TreeInfo **node_first)
1926 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1930 // ============================================================================
1931 // some stuff from "files.c"
1932 // ============================================================================
1934 #if defined(PLATFORM_WINDOWS)
1936 #define S_IRGRP S_IRUSR
1939 #define S_IROTH S_IRUSR
1942 #define S_IWGRP S_IWUSR
1945 #define S_IWOTH S_IWUSR
1948 #define S_IXGRP S_IXUSR
1951 #define S_IXOTH S_IXUSR
1954 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1959 #endif // PLATFORM_WINDOWS
1961 // file permissions for newly written files
1962 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1963 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1964 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1966 #define MODE_W_PRIVATE (S_IWUSR)
1967 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1968 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1970 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1971 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1972 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1974 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1975 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1976 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1979 char *getHomeDir(void)
1981 static char *dir = NULL;
1983 #if defined(PLATFORM_WINDOWS)
1986 dir = checked_malloc(MAX_PATH + 1);
1988 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1991 #elif defined(PLATFORM_EMSCRIPTEN)
1992 dir = PERSISTENT_DIRECTORY;
1993 #elif defined(PLATFORM_UNIX)
1996 if ((dir = getenv("HOME")) == NULL)
1998 dir = getUnixHomeDir();
2001 dir = getStringCopy(dir);
2013 char *getPersonalDataDir(void)
2015 static char *personal_data_dir = NULL;
2017 #if defined(PLATFORM_MAC)
2018 if (personal_data_dir == NULL)
2019 personal_data_dir = getPath2(getHomeDir(), "Documents");
2021 if (personal_data_dir == NULL)
2022 personal_data_dir = getHomeDir();
2025 return personal_data_dir;
2028 char *getMainUserGameDataDir(void)
2030 static char *main_user_data_dir = NULL;
2032 #if defined(PLATFORM_ANDROID)
2033 if (main_user_data_dir == NULL)
2034 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
2035 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
2036 SDL_AndroidGetExternalStoragePath() :
2037 SDL_AndroidGetInternalStoragePath());
2039 if (main_user_data_dir == NULL)
2040 main_user_data_dir = getPath2(getPersonalDataDir(),
2041 program.userdata_subdir);
2044 return main_user_data_dir;
2047 char *getUserGameDataDir(void)
2050 return getMainUserGameDataDir();
2052 return getUserDir(user.nr);
2055 char *getSetupDir(void)
2057 return getUserGameDataDir();
2060 static mode_t posix_umask(mode_t mask)
2062 #if defined(PLATFORM_UNIX)
2069 static int posix_mkdir(const char *pathname, mode_t mode)
2071 #if defined(PLATFORM_WINDOWS)
2072 return mkdir(pathname);
2074 return mkdir(pathname, mode);
2078 static boolean posix_process_running_setgid(void)
2080 #if defined(PLATFORM_UNIX)
2081 return (getgid() != getegid());
2087 void createDirectory(char *dir, char *text)
2089 if (directoryExists(dir))
2092 // leave "other" permissions in umask untouched, but ensure group parts
2093 // of USERDATA_DIR_MODE are not masked
2094 int permission_class = PERMS_PRIVATE;
2095 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
2096 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
2097 mode_t last_umask = posix_umask(0);
2098 mode_t group_umask = ~(dir_mode & S_IRWXG);
2099 int running_setgid = posix_process_running_setgid();
2101 if (permission_class == PERMS_PUBLIC)
2103 // if we're setgid, protect files against "other"
2104 // else keep umask(0) to make the dir world-writable
2107 posix_umask(last_umask & group_umask);
2109 dir_mode = DIR_PERMS_PUBLIC_ALL;
2112 if (posix_mkdir(dir, dir_mode) != 0)
2113 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
2115 if (permission_class == PERMS_PUBLIC && !running_setgid)
2116 chmod(dir, dir_mode);
2118 posix_umask(last_umask); // restore previous umask
2121 void InitMainUserDataDirectory(void)
2123 createDirectory(getMainUserGameDataDir(), "main user data");
2126 void InitUserDataDirectory(void)
2128 createDirectory(getMainUserGameDataDir(), "main user data");
2132 createDirectory(getUserDir(-1), "users");
2133 createDirectory(getUserDir(user.nr), "user data");
2137 void SetFilePermissions(char *filename, int permission_class)
2139 int running_setgid = posix_process_running_setgid();
2140 int perms = (permission_class == PERMS_PRIVATE ?
2141 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
2143 if (permission_class == PERMS_PUBLIC && !running_setgid)
2144 perms = FILE_PERMS_PUBLIC_ALL;
2146 chmod(filename, perms);
2149 void fprintFileHeader(FILE *file, char *basename)
2151 char *prefix = "# ";
2154 fprintf_line_with_prefix(file, prefix, sep1, 77);
2155 fprintf(file, "%s%s\n", prefix, basename);
2156 fprintf_line_with_prefix(file, prefix, sep1, 77);
2157 fprintf(file, "\n");
2160 int getFileVersionFromCookieString(const char *cookie)
2162 const char *ptr_cookie1, *ptr_cookie2;
2163 const char *pattern1 = "_FILE_VERSION_";
2164 const char *pattern2 = "?.?";
2165 const int len_cookie = strlen(cookie);
2166 const int len_pattern1 = strlen(pattern1);
2167 const int len_pattern2 = strlen(pattern2);
2168 const int len_pattern = len_pattern1 + len_pattern2;
2169 int version_super, version_major;
2171 if (len_cookie <= len_pattern)
2174 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2175 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2177 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2180 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2181 ptr_cookie2[1] != '.' ||
2182 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2185 version_super = ptr_cookie2[0] - '0';
2186 version_major = ptr_cookie2[2] - '0';
2188 return VERSION_IDENT(version_super, version_major, 0, 0);
2191 boolean checkCookieString(const char *cookie, const char *template)
2193 const char *pattern = "_FILE_VERSION_?.?";
2194 const int len_cookie = strlen(cookie);
2195 const int len_template = strlen(template);
2196 const int len_pattern = strlen(pattern);
2198 if (len_cookie != len_template)
2201 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2208 // ----------------------------------------------------------------------------
2209 // setup file list and hash handling functions
2210 // ----------------------------------------------------------------------------
2212 char *getFormattedSetupEntry(char *token, char *value)
2215 static char entry[MAX_LINE_LEN];
2217 // if value is an empty string, just return token without value
2221 // start with the token and some spaces to format output line
2222 sprintf(entry, "%s:", token);
2223 for (i = strlen(entry); i < token_value_position; i++)
2226 // continue with the token's value
2227 strcat(entry, value);
2232 SetupFileList *newSetupFileList(char *token, char *value)
2234 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2236 new->token = getStringCopy(token);
2237 new->value = getStringCopy(value);
2244 void freeSetupFileList(SetupFileList *list)
2249 checked_free(list->token);
2250 checked_free(list->value);
2253 freeSetupFileList(list->next);
2258 char *getListEntry(SetupFileList *list, char *token)
2263 if (strEqual(list->token, token))
2266 return getListEntry(list->next, token);
2269 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2274 if (strEqual(list->token, token))
2276 checked_free(list->value);
2278 list->value = getStringCopy(value);
2282 else if (list->next == NULL)
2283 return (list->next = newSetupFileList(token, value));
2285 return setListEntry(list->next, token, value);
2288 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2293 if (list->next == NULL)
2294 return (list->next = newSetupFileList(token, value));
2296 return addListEntry(list->next, token, value);
2299 #if ENABLE_UNUSED_CODE
2301 static void printSetupFileList(SetupFileList *list)
2306 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2307 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2309 printSetupFileList(list->next);
2315 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2316 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2317 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2318 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2320 #define insert_hash_entry hashtable_insert
2321 #define search_hash_entry hashtable_search
2322 #define change_hash_entry hashtable_change
2323 #define remove_hash_entry hashtable_remove
2326 unsigned int get_hash_from_string(void *key)
2331 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2332 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2333 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2334 it works better than many other constants, prime or not) has never been
2335 adequately explained.
2337 If you just want to have a good hash function, and cannot wait, djb2
2338 is one of the best string hash functions i know. It has excellent
2339 distribution and speed on many different sets of keys and table sizes.
2340 You are not likely to do better with one of the "well known" functions
2341 such as PJW, K&R, etc.
2343 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2346 char *str = (char *)key;
2347 unsigned int hash = 5381;
2350 while ((c = *str++))
2351 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2356 unsigned int get_hash_from_integer(void *key)
2358 unsigned int hash = PTR_TO_UINT(key);
2363 int hash_key_strings_are_equal(void *key1, void *key2)
2365 return (strEqual((char *)key1, (char *)key2));
2368 int hash_key_integers_are_equal(void *key1, void *key2)
2370 return (key1 == key2);
2373 SetupFileHash *newSetupFileHash(void)
2375 SetupFileHash *new_hash =
2376 create_hashtable(get_hash_from_string, hash_key_strings_are_equal, free, free);
2378 if (new_hash == NULL)
2379 Fail("create_hashtable() failed -- out of memory");
2384 void freeSetupFileHash(SetupFileHash *hash)
2389 hashtable_destroy(hash);
2392 char *getHashEntry(SetupFileHash *hash, char *token)
2397 return search_hash_entry(hash, token);
2400 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2407 value_copy = getStringCopy(value);
2409 // change value; if it does not exist, insert it as new
2410 if (!change_hash_entry(hash, token, value_copy))
2411 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2412 Fail("cannot insert into hash -- aborting");
2415 void removeHashEntry(SetupFileHash *hash, char *token)
2420 remove_hash_entry(hash, token);
2423 #if ENABLE_UNUSED_CODE
2425 static void printSetupFileHash(SetupFileHash *hash)
2427 BEGIN_HASH_ITERATION(hash, itr)
2429 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2430 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2432 END_HASH_ITERATION(hash, itr)
2437 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2438 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2439 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2441 static boolean token_value_separator_found = FALSE;
2442 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2443 static boolean token_value_separator_warning = FALSE;
2445 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2446 static boolean token_already_exists_warning = FALSE;
2449 static boolean getTokenValueFromSetupLineExt(char *line,
2450 char **token_ptr, char **value_ptr,
2451 char *filename, char *line_raw,
2453 boolean separator_required)
2455 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2456 char *token, *value, *line_ptr;
2458 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2459 if (line_raw == NULL)
2461 strncpy(line_copy, line, MAX_LINE_LEN);
2462 line_copy[MAX_LINE_LEN] = '\0';
2465 strcpy(line_raw_copy, line_copy);
2466 line_raw = line_raw_copy;
2469 // cut trailing comment from input line
2470 for (line_ptr = line; *line_ptr; line_ptr++)
2472 if (*line_ptr == '#')
2479 // cut trailing whitespaces from input line
2480 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2481 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2484 // ignore empty lines
2488 // cut leading whitespaces from token
2489 for (token = line; *token; token++)
2490 if (*token != ' ' && *token != '\t')
2493 // start with empty value as reliable default
2496 token_value_separator_found = FALSE;
2498 // find end of token to determine start of value
2499 for (line_ptr = token; *line_ptr; line_ptr++)
2501 // first look for an explicit token/value separator, like ':' or '='
2502 if (*line_ptr == ':' || *line_ptr == '=')
2504 *line_ptr = '\0'; // terminate token string
2505 value = line_ptr + 1; // set beginning of value
2507 token_value_separator_found = TRUE;
2513 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2514 // fallback: if no token/value separator found, also allow whitespaces
2515 if (!token_value_separator_found && !separator_required)
2517 for (line_ptr = token; *line_ptr; line_ptr++)
2519 if (*line_ptr == ' ' || *line_ptr == '\t')
2521 *line_ptr = '\0'; // terminate token string
2522 value = line_ptr + 1; // set beginning of value
2524 token_value_separator_found = TRUE;
2530 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2531 if (token_value_separator_found)
2533 if (!token_value_separator_warning)
2535 Debug("setup", "---");
2537 if (filename != NULL)
2539 Debug("setup", "missing token/value separator(s) in config file:");
2540 Debug("setup", "- config file: '%s'", filename);
2544 Debug("setup", "missing token/value separator(s):");
2547 token_value_separator_warning = TRUE;
2550 if (filename != NULL)
2551 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2553 Debug("setup", "- line: '%s'", line_raw);
2559 // cut trailing whitespaces from token
2560 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2561 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2564 // cut leading whitespaces from value
2565 for (; *value; value++)
2566 if (*value != ' ' && *value != '\t')
2575 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2577 // while the internal (old) interface does not require a token/value
2578 // separator (for downwards compatibility with existing files which
2579 // don't use them), it is mandatory for the external (new) interface
2581 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2584 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2585 boolean top_recursion_level, boolean is_hash)
2587 static SetupFileHash *include_filename_hash = NULL;
2588 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2589 char *token, *value, *line_ptr;
2590 void *insert_ptr = NULL;
2591 boolean read_continued_line = FALSE;
2593 int line_nr = 0, token_count = 0, include_count = 0;
2595 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2596 token_value_separator_warning = FALSE;
2599 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2600 token_already_exists_warning = FALSE;
2603 if (!(file = openFile(filename, MODE_READ)))
2605 #if DEBUG_NO_CONFIG_FILE
2606 Debug("setup", "cannot open configuration file '%s'", filename);
2612 // use "insert pointer" to store list end for constant insertion complexity
2614 insert_ptr = setup_file_data;
2616 // on top invocation, create hash to mark included files (to prevent loops)
2617 if (top_recursion_level)
2618 include_filename_hash = newSetupFileHash();
2620 // mark this file as already included (to prevent including it again)
2621 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2623 while (!checkEndOfFile(file))
2625 // read next line of input file
2626 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2629 // check if line was completely read and is terminated by line break
2630 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2633 // cut trailing line break (this can be newline and/or carriage return)
2634 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2635 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2638 // copy raw input line for later use (mainly debugging output)
2639 strcpy(line_raw, line);
2641 if (read_continued_line)
2643 // append new line to existing line, if there is enough space
2644 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2645 strcat(previous_line, line_ptr);
2647 strcpy(line, previous_line); // copy storage buffer to line
2649 read_continued_line = FALSE;
2652 // if the last character is '\', continue at next line
2653 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2655 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2656 strcpy(previous_line, line); // copy line to storage buffer
2658 read_continued_line = TRUE;
2663 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2664 line_raw, line_nr, FALSE))
2669 if (strEqual(token, "include"))
2671 if (getHashEntry(include_filename_hash, value) == NULL)
2673 char *basepath = getBasePath(filename);
2674 char *basename = getBaseName(value);
2675 char *filename_include = getPath2(basepath, basename);
2677 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2681 free(filename_include);
2687 Warn("ignoring already processed file '%s'", value);
2694 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2696 getHashEntry((SetupFileHash *)setup_file_data, token);
2698 if (old_value != NULL)
2700 if (!token_already_exists_warning)
2702 Debug("setup", "---");
2703 Debug("setup", "duplicate token(s) found in config file:");
2704 Debug("setup", "- config file: '%s'", filename);
2706 token_already_exists_warning = TRUE;
2709 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2710 Debug("setup", " old value: '%s'", old_value);
2711 Debug("setup", " new value: '%s'", value);
2715 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2719 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2729 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2730 if (token_value_separator_warning)
2731 Debug("setup", "---");
2734 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2735 if (token_already_exists_warning)
2736 Debug("setup", "---");
2739 if (token_count == 0 && include_count == 0)
2740 Warn("configuration file '%s' is empty", filename);
2742 if (top_recursion_level)
2743 freeSetupFileHash(include_filename_hash);
2748 static int compareSetupFileData(const void *object1, const void *object2)
2750 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2751 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2753 return strcmp(entry1->token, entry2->token);
2756 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2758 int item_count = hashtable_count(hash);
2759 int item_size = sizeof(struct ConfigInfo);
2760 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2764 // copy string pointers from hash to array
2765 BEGIN_HASH_ITERATION(hash, itr)
2767 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2768 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2772 if (i > item_count) // should never happen
2775 END_HASH_ITERATION(hash, itr)
2777 // sort string pointers from hash in array
2778 qsort(sort_array, item_count, item_size, compareSetupFileData);
2780 if (!(file = fopen(filename, MODE_WRITE)))
2782 Warn("cannot write configuration file '%s'", filename);
2787 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2788 program.version_string));
2789 for (i = 0; i < item_count; i++)
2790 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2791 sort_array[i].value));
2794 checked_free(sort_array);
2797 SetupFileList *loadSetupFileList(char *filename)
2799 SetupFileList *setup_file_list = newSetupFileList("", "");
2800 SetupFileList *first_valid_list_entry;
2802 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2804 freeSetupFileList(setup_file_list);
2809 first_valid_list_entry = setup_file_list->next;
2811 // free empty list header
2812 setup_file_list->next = NULL;
2813 freeSetupFileList(setup_file_list);
2815 return first_valid_list_entry;
2818 SetupFileHash *loadSetupFileHash(char *filename)
2820 SetupFileHash *setup_file_hash = newSetupFileHash();
2822 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2824 freeSetupFileHash(setup_file_hash);
2829 return setup_file_hash;
2833 // ============================================================================
2835 // ============================================================================
2837 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2838 #define TOKEN_STR_LAST_PLAYED_MENU_USED "last_played_menu_used"
2839 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2840 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2841 #define TOKEN_STR_LAST_USER "last_user"
2843 // level directory info
2844 #define LEVELINFO_TOKEN_IDENTIFIER 0
2845 #define LEVELINFO_TOKEN_NAME 1
2846 #define LEVELINFO_TOKEN_NAME_SORTING 2
2847 #define LEVELINFO_TOKEN_AUTHOR 3
2848 #define LEVELINFO_TOKEN_YEAR 4
2849 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2850 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2851 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2852 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2853 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2854 #define LEVELINFO_TOKEN_TESTED_BY 10
2855 #define LEVELINFO_TOKEN_LEVELS 11
2856 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2857 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2858 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2859 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2860 #define LEVELINFO_TOKEN_READONLY 16
2861 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2862 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2863 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2864 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2865 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2866 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2867 #define LEVELINFO_TOKEN_MUSIC_SET 23
2868 #define LEVELINFO_TOKEN_FILENAME 24
2869 #define LEVELINFO_TOKEN_FILETYPE 25
2870 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2871 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2872 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2873 #define LEVELINFO_TOKEN_HANDICAP 29
2874 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2875 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2876 #define LEVELINFO_TOKEN_ALLOW_SKIPPING_LEVELS 32
2877 #define LEVELINFO_TOKEN_USE_EMC_TILES 33
2878 #define LEVELINFO_TOKEN_INFO_SCREENS_FROM_MAIN 34
2880 #define NUM_LEVELINFO_TOKENS 35
2882 static LevelDirTree ldi;
2884 static struct TokenInfo levelinfo_tokens[] =
2886 // level directory info
2887 { TYPE_STRING, &ldi.identifier, "identifier" },
2888 { TYPE_STRING, &ldi.name, "name" },
2889 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2890 { TYPE_STRING, &ldi.author, "author" },
2891 { TYPE_STRING, &ldi.year, "year" },
2892 { TYPE_STRING, &ldi.program_title, "program_title" },
2893 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2894 { TYPE_STRING, &ldi.program_company, "program_company" },
2895 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2896 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2897 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2898 { TYPE_INTEGER, &ldi.levels, "levels" },
2899 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2900 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2901 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2902 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2903 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2904 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.old" },
2905 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.new" },
2906 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2907 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2908 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2909 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2910 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2911 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2912 { TYPE_STRING, &ldi.music_set, "music_set" },
2913 { TYPE_STRING, &ldi.level_filename, "filename" },
2914 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2915 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2916 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2917 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2918 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2919 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2920 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2921 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" },
2922 { TYPE_BOOLEAN, &ldi.info_screens_from_main, "info_screens_from_main" }
2925 static struct TokenInfo artworkinfo_tokens[] =
2927 // artwork directory info
2928 { TYPE_STRING, &ldi.identifier, "identifier" },
2929 { TYPE_STRING, &ldi.subdir, "subdir" },
2930 { TYPE_STRING, &ldi.name, "name" },
2931 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2932 { TYPE_STRING, &ldi.author, "author" },
2933 { TYPE_STRING, &ldi.program_title, "program_title" },
2934 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2935 { TYPE_STRING, &ldi.program_company, "program_company" },
2936 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2937 { TYPE_STRING, &ldi.basepath, "basepath" },
2938 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2939 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2940 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2945 static char *optional_tokens[] =
2948 "program_copyright",
2954 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2958 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2959 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2960 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2961 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2964 ti->node_parent = NULL;
2965 ti->node_group = NULL;
2972 ti->fullpath = NULL;
2973 ti->basepath = NULL;
2974 ti->identifier = NULL;
2975 ti->name = getStringCopy(ANONYMOUS_NAME);
2976 ti->name_sorting = NULL;
2977 ti->author = getStringCopy(ANONYMOUS_NAME);
2980 ti->program_title = NULL;
2981 ti->program_copyright = NULL;
2982 ti->program_company = NULL;
2984 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2985 ti->latest_engine = FALSE; // default: get from level
2986 ti->parent_link = FALSE;
2987 ti->is_copy = FALSE;
2988 ti->in_user_dir = FALSE;
2989 ti->user_defined = FALSE;
2991 ti->class_desc = NULL;
2993 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2995 if (ti->type == TREE_TYPE_LEVEL_DIR)
2997 ti->imported_from = NULL;
2998 ti->imported_by = NULL;
2999 ti->tested_by = NULL;
3001 ti->graphics_set_ecs = NULL;
3002 ti->graphics_set_aga = NULL;
3003 ti->graphics_set = NULL;
3004 ti->sounds_set_default = NULL;
3005 ti->sounds_set_lowpass = NULL;
3006 ti->sounds_set = NULL;
3007 ti->music_set = NULL;
3008 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3009 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3010 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3012 ti->level_filename = NULL;
3013 ti->level_filetype = NULL;
3015 ti->special_flags = NULL;
3017 ti->empty_level_name = NULL;
3018 ti->force_level_name = FALSE;
3021 ti->first_level = 0;
3023 ti->level_group = FALSE;
3024 ti->handicap_level = 0;
3025 ti->readonly = TRUE;
3026 ti->handicap = TRUE;
3027 ti->time_limit = TRUE;
3028 ti->skip_levels = FALSE;
3030 ti->use_emc_tiles = FALSE;
3031 ti->info_screens_from_main = FALSE;
3035 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
3039 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
3041 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
3046 // copy all values from the parent structure
3048 ti->type = parent->type;
3050 ti->node_top = parent->node_top;
3051 ti->node_parent = parent;
3052 ti->node_group = NULL;
3059 ti->fullpath = NULL;
3060 ti->basepath = NULL;
3061 ti->identifier = NULL;
3062 ti->name = getStringCopy(ANONYMOUS_NAME);
3063 ti->name_sorting = NULL;
3064 ti->author = getStringCopy(parent->author);
3065 ti->year = getStringCopy(parent->year);
3067 ti->program_title = getStringCopy(parent->program_title);
3068 ti->program_copyright = getStringCopy(parent->program_copyright);
3069 ti->program_company = getStringCopy(parent->program_company);
3071 ti->sort_priority = parent->sort_priority;
3072 ti->latest_engine = parent->latest_engine;
3073 ti->parent_link = FALSE;
3074 ti->is_copy = FALSE;
3075 ti->in_user_dir = parent->in_user_dir;
3076 ti->user_defined = parent->user_defined;
3077 ti->color = parent->color;
3078 ti->class_desc = getStringCopy(parent->class_desc);
3080 ti->infotext = getStringCopy(parent->infotext);
3082 if (ti->type == TREE_TYPE_LEVEL_DIR)
3084 ti->imported_from = getStringCopy(parent->imported_from);
3085 ti->imported_by = getStringCopy(parent->imported_by);
3086 ti->tested_by = getStringCopy(parent->tested_by);
3088 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
3089 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
3090 ti->graphics_set = getStringCopy(parent->graphics_set);
3091 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
3092 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
3093 ti->sounds_set = getStringCopy(parent->sounds_set);
3094 ti->music_set = getStringCopy(parent->music_set);
3095 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
3096 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
3097 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
3099 ti->level_filename = getStringCopy(parent->level_filename);
3100 ti->level_filetype = getStringCopy(parent->level_filetype);
3102 ti->special_flags = getStringCopy(parent->special_flags);
3104 ti->empty_level_name = getStringCopy(parent->empty_level_name);
3105 ti->force_level_name = parent->force_level_name;
3107 ti->levels = parent->levels;
3108 ti->first_level = parent->first_level;
3109 ti->last_level = parent->last_level;
3110 ti->level_group = FALSE;
3111 ti->handicap_level = parent->handicap_level;
3112 ti->readonly = parent->readonly;
3113 ti->handicap = parent->handicap;
3114 ti->time_limit = parent->time_limit;
3115 ti->skip_levels = parent->skip_levels;
3117 ti->use_emc_tiles = parent->use_emc_tiles;
3118 ti->info_screens_from_main = parent->info_screens_from_main;
3122 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
3124 TreeInfo *ti_copy = newTreeInfo();
3126 // copy all values from the original structure
3128 ti_copy->type = ti->type;
3130 ti_copy->node_top = ti->node_top;
3131 ti_copy->node_parent = ti->node_parent;
3132 ti_copy->node_group = ti->node_group;
3133 ti_copy->next = ti->next;
3135 ti_copy->cl_first = ti->cl_first;
3136 ti_copy->cl_cursor = ti->cl_cursor;
3138 ti_copy->subdir = getStringCopy(ti->subdir);
3139 ti_copy->fullpath = getStringCopy(ti->fullpath);
3140 ti_copy->basepath = getStringCopy(ti->basepath);
3141 ti_copy->identifier = getStringCopy(ti->identifier);
3142 ti_copy->name = getStringCopy(ti->name);
3143 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
3144 ti_copy->author = getStringCopy(ti->author);
3145 ti_copy->year = getStringCopy(ti->year);
3147 ti_copy->program_title = getStringCopy(ti->program_title);
3148 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
3149 ti_copy->program_company = getStringCopy(ti->program_company);
3151 ti_copy->imported_from = getStringCopy(ti->imported_from);
3152 ti_copy->imported_by = getStringCopy(ti->imported_by);
3153 ti_copy->tested_by = getStringCopy(ti->tested_by);
3155 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
3156 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
3157 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
3158 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
3159 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
3160 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
3161 ti_copy->music_set = getStringCopy(ti->music_set);
3162 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
3163 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
3164 ti_copy->music_path = getStringCopy(ti->music_path);
3166 ti_copy->level_filename = getStringCopy(ti->level_filename);
3167 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3169 ti_copy->special_flags = getStringCopy(ti->special_flags);
3171 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3172 ti_copy->force_level_name = ti->force_level_name;
3174 ti_copy->levels = ti->levels;
3175 ti_copy->first_level = ti->first_level;
3176 ti_copy->last_level = ti->last_level;
3177 ti_copy->sort_priority = ti->sort_priority;
3179 ti_copy->latest_engine = ti->latest_engine;
3181 ti_copy->level_group = ti->level_group;
3182 ti_copy->parent_link = ti->parent_link;
3183 ti_copy->is_copy = ti->is_copy;
3184 ti_copy->in_user_dir = ti->in_user_dir;
3185 ti_copy->user_defined = ti->user_defined;
3186 ti_copy->readonly = ti->readonly;
3187 ti_copy->handicap = ti->handicap;
3188 ti_copy->time_limit = ti->time_limit;
3189 ti_copy->skip_levels = ti->skip_levels;
3191 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3192 ti_copy->info_screens_from_main = ti->info_screens_from_main;
3194 ti_copy->color = ti->color;
3195 ti_copy->class_desc = getStringCopy(ti->class_desc);
3196 ti_copy->handicap_level = ti->handicap_level;
3198 ti_copy->infotext = getStringCopy(ti->infotext);
3203 void freeTreeInfo(TreeInfo *ti)
3208 checked_free(ti->subdir);
3209 checked_free(ti->fullpath);
3210 checked_free(ti->basepath);
3211 checked_free(ti->identifier);
3213 checked_free(ti->name);
3214 checked_free(ti->name_sorting);
3215 checked_free(ti->author);
3216 checked_free(ti->year);
3218 checked_free(ti->program_title);
3219 checked_free(ti->program_copyright);
3220 checked_free(ti->program_company);
3222 checked_free(ti->class_desc);
3224 checked_free(ti->infotext);
3226 if (ti->type == TREE_TYPE_LEVEL_DIR)
3228 checked_free(ti->imported_from);
3229 checked_free(ti->imported_by);
3230 checked_free(ti->tested_by);
3232 checked_free(ti->graphics_set_ecs);
3233 checked_free(ti->graphics_set_aga);
3234 checked_free(ti->graphics_set);
3235 checked_free(ti->sounds_set_default);
3236 checked_free(ti->sounds_set_lowpass);
3237 checked_free(ti->sounds_set);
3238 checked_free(ti->music_set);
3240 checked_free(ti->graphics_path);
3241 checked_free(ti->sounds_path);
3242 checked_free(ti->music_path);
3244 checked_free(ti->level_filename);
3245 checked_free(ti->level_filetype);
3247 checked_free(ti->special_flags);
3250 // recursively free child node
3252 freeTreeInfo(ti->node_group);
3254 // recursively free next node
3256 freeTreeInfo(ti->next);
3261 void setSetupInfo(struct TokenInfo *token_info,
3262 int token_nr, char *token_value)
3264 int token_type = token_info[token_nr].type;
3265 void *setup_value = token_info[token_nr].value;
3267 if (token_value == NULL)
3270 // set setup field to corresponding token value
3275 *(boolean *)setup_value = get_boolean_from_string(token_value);
3278 case TYPE_SWITCH_3_STATES:
3279 *(int *)setup_value = get_switch_3_state_from_string(token_value);
3283 *(Key *)setup_value = getKeyFromKeyName(token_value);
3287 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3291 *(int *)setup_value = get_integer_from_string(token_value);
3295 checked_free(*(char **)setup_value);
3296 *(char **)setup_value = getStringCopy(token_value);
3300 *(int *)setup_value = get_player_nr_from_string(token_value);
3308 static int compareTreeInfoEntries(const void *object1, const void *object2)
3310 const TreeInfo *entry1 = *((TreeInfo **)object1);
3311 const TreeInfo *entry2 = *((TreeInfo **)object2);
3312 int tree_sorting1 = TREE_SORTING(entry1);
3313 int tree_sorting2 = TREE_SORTING(entry2);
3315 if (tree_sorting1 != tree_sorting2)
3316 return (tree_sorting1 - tree_sorting2);
3318 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3321 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3325 if (node_parent == NULL)
3328 ti_new = newTreeInfo();
3329 setTreeInfoToDefaults(ti_new, node_parent->type);
3331 ti_new->node_parent = node_parent;
3332 ti_new->parent_link = TRUE;
3334 setString(&ti_new->identifier, node_parent->identifier);
3335 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3336 setString(&ti_new->name_sorting, ti_new->name);
3338 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3339 setString(&ti_new->fullpath, node_parent->fullpath);
3341 ti_new->sort_priority = LEVELCLASS_PARENT;
3342 ti_new->latest_engine = node_parent->latest_engine;
3344 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3346 pushTreeInfo(&node_parent->node_group, ti_new);
3351 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3353 if (node_first == NULL)
3356 TreeInfo *ti_new = newTreeInfo();
3357 int type = node_first->type;
3359 setTreeInfoToDefaults(ti_new, type);
3361 ti_new->node_parent = NULL;
3362 ti_new->parent_link = FALSE;
3364 setString(&ti_new->identifier, "top_tree_node");
3365 setString(&ti_new->name, TREE_INFOTEXT(type));
3366 setString(&ti_new->name_sorting, ti_new->name);
3368 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3369 setString(&ti_new->fullpath, ".");
3371 ti_new->sort_priority = LEVELCLASS_TOP;
3372 ti_new->latest_engine = node_first->latest_engine;
3374 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3376 ti_new->node_group = node_first;
3377 ti_new->level_group = TRUE;
3379 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3381 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3382 setString(&ti_new2->name_sorting, ti_new2->name);
3387 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3391 if (node->node_group)
3392 setTreeInfoParentNodes(node->node_group, node);
3394 node->node_parent = node_parent;
3400 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3402 // add top tree node with back link node in previous tree
3403 node_first = createTopTreeInfoNode(node_first);
3405 // set all parent links (back links) in complete tree
3406 setTreeInfoParentNodes(node_first, NULL);
3412 // ----------------------------------------------------------------------------
3413 // functions for handling level and custom artwork info cache
3414 // ----------------------------------------------------------------------------
3416 static void LoadArtworkInfoCache(void)
3418 InitCacheDirectory();
3420 if (artworkinfo_cache_old == NULL)
3422 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3424 // try to load artwork info hash from already existing cache file
3425 artworkinfo_cache_old = loadSetupFileHash(filename);
3427 // try to get program version that artwork info cache was written with
3428 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3430 // check program version of artwork info cache against current version
3431 if (!strEqual(version, program.version_string))
3433 freeSetupFileHash(artworkinfo_cache_old);
3435 artworkinfo_cache_old = NULL;
3438 // if no artwork info cache file was found, start with empty hash
3439 if (artworkinfo_cache_old == NULL)
3440 artworkinfo_cache_old = newSetupFileHash();
3445 if (artworkinfo_cache_new == NULL)
3446 artworkinfo_cache_new = newSetupFileHash();
3448 update_artworkinfo_cache = FALSE;
3451 static void SaveArtworkInfoCache(void)
3453 if (!update_artworkinfo_cache)
3456 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3458 InitCacheDirectory();
3460 saveSetupFileHash(artworkinfo_cache_new, filename);
3465 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3467 static char *prefix = NULL;
3469 checked_free(prefix);
3471 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3476 // (identical to above function, but separate string buffer needed -- nasty)
3477 static char *getCacheToken(char *prefix, char *suffix)
3479 static char *token = NULL;
3481 checked_free(token);
3483 token = getStringCat2WithSeparator(prefix, suffix, ".");
3488 static char *getFileTimestampString(char *filename)
3490 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3493 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3495 struct stat file_status;
3497 if (timestamp_string == NULL)
3500 if (!fileExists(filename)) // file does not exist
3501 return (atoi(timestamp_string) != 0);
3503 if (stat(filename, &file_status) != 0) // cannot stat file
3506 return (file_status.st_mtime != atoi(timestamp_string));
3509 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3511 char *identifier = level_node->subdir;
3512 char *type_string = ARTWORK_DIRECTORY(type);
3513 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3514 char *token_main = getCacheToken(token_prefix, "CACHED");
3515 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3516 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3517 TreeInfo *artwork_info = NULL;
3519 if (!use_artworkinfo_cache)
3522 if (optional_tokens_hash == NULL)
3526 // create hash from list of optional tokens (for quick access)
3527 optional_tokens_hash = newSetupFileHash();
3528 for (i = 0; optional_tokens[i] != NULL; i++)
3529 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3536 artwork_info = newTreeInfo();
3537 setTreeInfoToDefaults(artwork_info, type);
3539 // set all structure fields according to the token/value pairs
3540 ldi = *artwork_info;
3541 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3543 char *token_suffix = artworkinfo_tokens[i].text;
3544 char *token = getCacheToken(token_prefix, token_suffix);
3545 char *value = getHashEntry(artworkinfo_cache_old, token);
3547 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3549 setSetupInfo(artworkinfo_tokens, i, value);
3551 // check if cache entry for this item is mandatory, but missing
3552 if (value == NULL && !optional)
3554 Warn("missing cache entry '%s'", token);
3560 *artwork_info = ldi;
3565 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3566 LEVELINFO_FILENAME);
3567 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3568 ARTWORKINFO_FILENAME(type));
3570 // check if corresponding "levelinfo.conf" file has changed
3571 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3572 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3574 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3577 // check if corresponding "<artworkinfo>.conf" file has changed
3578 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3579 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3581 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3584 checked_free(filename_levelinfo);
3585 checked_free(filename_artworkinfo);
3588 if (!cached && artwork_info != NULL)
3590 freeTreeInfo(artwork_info);
3595 return artwork_info;
3598 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3599 LevelDirTree *level_node, int type)
3601 char *identifier = level_node->subdir;
3602 char *type_string = ARTWORK_DIRECTORY(type);
3603 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3604 char *token_main = getCacheToken(token_prefix, "CACHED");
3605 boolean set_cache_timestamps = TRUE;
3608 setHashEntry(artworkinfo_cache_new, token_main, "true");
3610 if (set_cache_timestamps)
3612 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3613 LEVELINFO_FILENAME);
3614 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3615 ARTWORKINFO_FILENAME(type));
3616 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3617 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3619 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3620 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3622 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3623 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3625 checked_free(filename_levelinfo);
3626 checked_free(filename_artworkinfo);
3627 checked_free(timestamp_levelinfo);
3628 checked_free(timestamp_artworkinfo);
3631 ldi = *artwork_info;
3632 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3634 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3635 char *value = getSetupValue(artworkinfo_tokens[i].type,
3636 artworkinfo_tokens[i].value);
3638 setHashEntry(artworkinfo_cache_new, token, value);
3643 // ----------------------------------------------------------------------------
3644 // functions for loading level info and custom artwork info
3645 // ----------------------------------------------------------------------------
3647 int GetZipFileTreeType(char *zip_filename)
3649 static char *top_dir_path = NULL;
3650 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3651 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3653 GRAPHICSINFO_FILENAME,
3654 SOUNDSINFO_FILENAME,
3660 checked_free(top_dir_path);
3661 top_dir_path = NULL;
3663 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3665 checked_free(top_dir_conf_filename[j]);
3666 top_dir_conf_filename[j] = NULL;
3669 char **zip_entries = zip_list(zip_filename);
3671 // check if zip file successfully opened
3672 if (zip_entries == NULL || zip_entries[0] == NULL)
3673 return TREE_TYPE_UNDEFINED;
3675 // first zip file entry is expected to be top level directory
3676 char *top_dir = zip_entries[0];
3678 // check if valid top level directory found in zip file
3679 if (!strSuffix(top_dir, "/"))
3680 return TREE_TYPE_UNDEFINED;
3682 // get filenames of valid configuration files in top level directory
3683 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3684 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3686 int tree_type = TREE_TYPE_UNDEFINED;
3689 while (zip_entries[e] != NULL)
3691 // check if every zip file entry is below top level directory
3692 if (!strPrefix(zip_entries[e], top_dir))
3693 return TREE_TYPE_UNDEFINED;
3695 // check if this zip file entry is a valid configuration filename
3696 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3698 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3700 // only exactly one valid configuration file allowed
3701 if (tree_type != TREE_TYPE_UNDEFINED)
3702 return TREE_TYPE_UNDEFINED;
3714 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3717 static char *top_dir_path = NULL;
3718 static char *top_dir_conf_filename = NULL;
3720 checked_free(top_dir_path);
3721 checked_free(top_dir_conf_filename);
3723 top_dir_path = NULL;
3724 top_dir_conf_filename = NULL;
3726 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3727 ARTWORKINFO_FILENAME(tree_type));
3729 // check if valid configuration filename determined
3730 if (conf_basename == NULL || strEqual(conf_basename, ""))
3733 char **zip_entries = zip_list(zip_filename);
3735 // check if zip file successfully opened
3736 if (zip_entries == NULL || zip_entries[0] == NULL)
3739 // first zip file entry is expected to be top level directory
3740 char *top_dir = zip_entries[0];
3742 // check if valid top level directory found in zip file
3743 if (!strSuffix(top_dir, "/"))
3746 // get path of extracted top level directory
3747 top_dir_path = getPath2(directory, top_dir);
3749 // remove trailing directory separator from top level directory path
3750 // (required to be able to check for file and directory in next step)
3751 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3753 // check if zip file's top level directory already exists in target directory
3754 if (fileExists(top_dir_path)) // (checks for file and directory)
3757 // get filename of configuration file in top level directory
3758 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3760 boolean found_top_dir_conf_filename = FALSE;
3763 while (zip_entries[i] != NULL)
3765 // check if every zip file entry is below top level directory
3766 if (!strPrefix(zip_entries[i], top_dir))
3769 // check if this zip file entry is the configuration filename
3770 if (strEqual(zip_entries[i], top_dir_conf_filename))
3771 found_top_dir_conf_filename = TRUE;
3776 // check if valid configuration filename was found in zip file
3777 if (!found_top_dir_conf_filename)
3783 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3786 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3789 if (!zip_file_valid)
3791 Warn("zip file '%s' rejected!", zip_filename);
3796 char **zip_entries = zip_extract(zip_filename, directory);
3798 if (zip_entries == NULL)
3800 Warn("zip file '%s' could not be extracted!", zip_filename);
3805 Info("zip file '%s' successfully extracted!", zip_filename);
3807 // first zip file entry contains top level directory
3808 char *top_dir = zip_entries[0];
3810 // remove trailing directory separator from top level directory
3811 top_dir[strlen(top_dir) - 1] = '\0';
3816 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3819 DirectoryEntry *dir_entry;
3821 if ((dir = openDirectory(directory)) == NULL)
3823 // display error if directory is main "options.graphics_directory" etc.
3824 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3825 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3826 Warn("cannot read directory '%s'", directory);
3831 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3833 // skip non-zip files (and also directories with zip extension)
3834 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3837 char *zip_filename = getPath2(directory, dir_entry->basename);
3838 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3839 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3841 // check if zip file hasn't already been extracted or rejected
3842 if (!fileExists(zip_filename_extracted) &&
3843 !fileExists(zip_filename_rejected))
3845 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3847 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3848 zip_filename_rejected);
3851 // create empty file to mark zip file as extracted or rejected
3852 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3853 fclose(marker_file);
3856 free(zip_filename_extracted);
3857 free(zip_filename_rejected);
3861 closeDirectory(dir);
3864 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3865 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3867 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3868 TreeInfo *node_parent,
3869 char *level_directory,
3870 char *directory_name)
3872 char *directory_path = getPath2(level_directory, directory_name);
3873 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3874 SetupFileHash *setup_file_hash;
3875 LevelDirTree *leveldir_new = NULL;
3878 // unless debugging, silently ignore directories without "levelinfo.conf"
3879 if (!options.debug && !fileExists(filename))
3881 free(directory_path);
3887 setup_file_hash = loadSetupFileHash(filename);
3889 if (setup_file_hash == NULL)
3891 #if DEBUG_NO_CONFIG_FILE
3892 Debug("setup", "ignoring level directory '%s'", directory_path);
3895 free(directory_path);
3901 leveldir_new = newTreeInfo();
3904 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3906 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3908 leveldir_new->subdir = getStringCopy(directory_name);
3910 // set all structure fields according to the token/value pairs
3911 ldi = *leveldir_new;
3912 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3913 setSetupInfo(levelinfo_tokens, i,
3914 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3915 *leveldir_new = ldi;
3917 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3918 setString(&leveldir_new->name, leveldir_new->subdir);
3920 if (leveldir_new->identifier == NULL)
3921 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3923 if (leveldir_new->name_sorting == NULL)
3924 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3926 if (node_parent == NULL) // top level group
3928 leveldir_new->basepath = getStringCopy(level_directory);
3929 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3931 else // sub level group
3933 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3934 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3937 leveldir_new->last_level =
3938 leveldir_new->first_level + leveldir_new->levels - 1;
3940 leveldir_new->in_user_dir =
3941 (!strEqual(leveldir_new->basepath, options.level_directory));
3943 // adjust some settings if user's private level directory was detected
3944 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3945 leveldir_new->in_user_dir &&
3946 (strEqual(leveldir_new->subdir, getLoginName()) ||
3947 strEqual(leveldir_new->name, getLoginName()) ||
3948 strEqual(leveldir_new->author, getRealName())))
3950 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3951 leveldir_new->readonly = FALSE;
3954 leveldir_new->user_defined =
3955 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3957 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3959 leveldir_new->handicap_level = // set handicap to default value
3960 (leveldir_new->user_defined || !leveldir_new->handicap ?
3961 leveldir_new->last_level : leveldir_new->first_level);
3963 DrawInitTextItem(leveldir_new->name);
3965 pushTreeInfo(node_first, leveldir_new);
3967 freeSetupFileHash(setup_file_hash);
3969 if (leveldir_new->level_group)
3971 // create node to link back to current level directory
3972 createParentTreeInfoNode(leveldir_new);
3974 // recursively step into sub-directory and look for more level series
3975 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3976 leveldir_new, directory_path);
3979 free(directory_path);
3985 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3986 TreeInfo *node_parent,
3987 char *level_directory)
3989 // ---------- 1st stage: process any level set zip files ----------
3991 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3993 // ---------- 2nd stage: check for level set directories ----------
3996 DirectoryEntry *dir_entry;
3997 boolean valid_entry_found = FALSE;
3999 if ((dir = openDirectory(level_directory)) == NULL)
4001 Warn("cannot read level directory '%s'", level_directory);
4006 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4008 char *directory_name = dir_entry->basename;
4009 char *directory_path = getPath2(level_directory, directory_name);
4011 // skip entries for current and parent directory
4012 if (strEqual(directory_name, ".") ||
4013 strEqual(directory_name, ".."))
4015 free(directory_path);
4020 // find out if directory entry is itself a directory
4021 if (!dir_entry->is_directory) // not a directory
4023 free(directory_path);
4028 free(directory_path);
4030 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
4031 strEqual(directory_name, SOUNDS_DIRECTORY) ||
4032 strEqual(directory_name, MUSIC_DIRECTORY))
4035 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
4040 closeDirectory(dir);
4042 // special case: top level directory may directly contain "levelinfo.conf"
4043 if (node_parent == NULL && !valid_entry_found)
4045 // check if this directory directly contains a file "levelinfo.conf"
4046 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
4047 level_directory, ".");
4050 boolean valid_entry_expected =
4051 (strEqual(level_directory, options.level_directory) ||
4052 setup.internal.create_user_levelset);
4054 if (valid_entry_expected && !valid_entry_found)
4055 Warn("cannot find any valid level series in directory '%s'",
4059 boolean AdjustGraphicsForEMC(void)
4061 boolean settings_changed = FALSE;
4063 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
4064 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
4066 return settings_changed;
4069 boolean AdjustSoundsForEMC(void)
4071 boolean settings_changed = FALSE;
4073 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
4074 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
4076 return settings_changed;
4079 void LoadLevelInfo(void)
4081 InitUserLevelDirectory(getLoginName());
4083 DrawInitTextHead("Loading level series");
4085 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
4086 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
4088 leveldir_first = createTopTreeInfoNode(leveldir_first);
4090 /* after loading all level set information, clone the level directory tree
4091 and remove all level sets without levels (these may still contain artwork
4092 to be offered in the setup menu as "custom artwork", and are therefore
4093 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
4094 leveldir_first_all = leveldir_first;
4095 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
4097 AdjustGraphicsForEMC();
4098 AdjustSoundsForEMC();
4100 // before sorting, the first entries will be from the user directory
4101 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4103 if (leveldir_first == NULL)
4104 Fail("cannot find any valid level series in any directory");
4106 sortTreeInfo(&leveldir_first);
4108 #if ENABLE_UNUSED_CODE
4109 dumpTreeInfo(leveldir_first, 0);
4113 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
4114 TreeInfo *node_parent,
4115 char *base_directory,
4116 char *directory_name, int type)
4118 char *directory_path = getPath2(base_directory, directory_name);
4119 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
4120 SetupFileHash *setup_file_hash = NULL;
4121 TreeInfo *artwork_new = NULL;
4124 if (fileExists(filename))
4125 setup_file_hash = loadSetupFileHash(filename);
4127 if (setup_file_hash == NULL) // no config file -- look for artwork files
4130 DirectoryEntry *dir_entry;
4131 boolean valid_file_found = FALSE;
4133 if ((dir = openDirectory(directory_path)) != NULL)
4135 while ((dir_entry = readDirectory(dir)) != NULL)
4137 if (FileIsArtworkType(dir_entry->filename, type))
4139 valid_file_found = TRUE;
4145 closeDirectory(dir);
4148 if (!valid_file_found)
4150 #if DEBUG_NO_CONFIG_FILE
4151 if (!strEqual(directory_name, "."))
4152 Debug("setup", "ignoring artwork directory '%s'", directory_path);
4155 free(directory_path);
4162 artwork_new = newTreeInfo();
4165 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4167 setTreeInfoToDefaults(artwork_new, type);
4169 artwork_new->subdir = getStringCopy(directory_name);
4171 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4173 // set all structure fields according to the token/value pairs
4175 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4176 setSetupInfo(levelinfo_tokens, i,
4177 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4180 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4181 setString(&artwork_new->name, artwork_new->subdir);
4183 if (artwork_new->identifier == NULL)
4184 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4186 if (artwork_new->name_sorting == NULL)
4187 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4190 if (node_parent == NULL) // top level group
4192 artwork_new->basepath = getStringCopy(base_directory);
4193 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4195 else // sub level group
4197 artwork_new->basepath = getStringCopy(node_parent->basepath);
4198 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4201 artwork_new->in_user_dir =
4202 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4204 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4206 if (setup_file_hash == NULL) // (after determining ".user_defined")
4208 if (strEqual(artwork_new->subdir, "."))
4210 if (artwork_new->user_defined)
4212 setString(&artwork_new->identifier, "private");
4213 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4217 setString(&artwork_new->identifier, "classic");
4218 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4221 setString(&artwork_new->class_desc,
4222 getLevelClassDescription(artwork_new));
4226 setString(&artwork_new->identifier, artwork_new->subdir);
4229 setString(&artwork_new->name, artwork_new->identifier);
4230 setString(&artwork_new->name_sorting, artwork_new->name);
4233 pushTreeInfo(node_first, artwork_new);
4235 freeSetupFileHash(setup_file_hash);
4237 free(directory_path);
4243 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4244 TreeInfo *node_parent,
4245 char *base_directory, int type)
4247 // ---------- 1st stage: process any artwork set zip files ----------
4249 ProcessZipFilesInDirectory(base_directory, type);
4251 // ---------- 2nd stage: check for artwork set directories ----------
4254 DirectoryEntry *dir_entry;
4255 boolean valid_entry_found = FALSE;
4257 if ((dir = openDirectory(base_directory)) == NULL)
4259 // display error if directory is main "options.graphics_directory" etc.
4260 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4261 Warn("cannot read directory '%s'", base_directory);
4266 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4268 char *directory_name = dir_entry->basename;
4269 char *directory_path = getPath2(base_directory, directory_name);
4271 // skip directory entries for current and parent directory
4272 if (strEqual(directory_name, ".") ||
4273 strEqual(directory_name, ".."))
4275 free(directory_path);
4280 // skip directory entries which are not a directory
4281 if (!dir_entry->is_directory) // not a directory
4283 free(directory_path);
4288 free(directory_path);
4290 // check if this directory contains artwork with or without config file
4291 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4293 directory_name, type);
4296 closeDirectory(dir);
4298 // check if this directory directly contains artwork itself
4299 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4300 base_directory, ".",
4302 if (!valid_entry_found)
4303 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4306 static TreeInfo *getDummyArtworkInfo(int type)
4308 // this is only needed when there is completely no artwork available
4309 TreeInfo *artwork_new = newTreeInfo();
4311 setTreeInfoToDefaults(artwork_new, type);
4313 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4314 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4315 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4317 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4318 setString(&artwork_new->name, UNDEFINED_FILENAME);
4319 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4324 void SetCurrentArtwork(int type)
4326 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4327 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4328 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4329 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4331 // set current artwork to artwork configured in setup menu
4332 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4334 // if not found, set current artwork to default artwork
4335 if (*current_ptr == NULL)
4336 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4338 // if not found, set current artwork to first artwork in tree
4339 if (*current_ptr == NULL)
4340 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4343 void ChangeCurrentArtworkIfNeeded(int type)
4345 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4346 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4348 if (!strEqual(current_identifier, setup_set))
4349 SetCurrentArtwork(type);
4352 void LoadArtworkInfo(void)
4354 LoadArtworkInfoCache();
4356 DrawInitTextHead("Looking for custom artwork");
4358 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4359 options.graphics_directory,
4360 TREE_TYPE_GRAPHICS_DIR);
4361 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4362 getUserGraphicsDir(),
4363 TREE_TYPE_GRAPHICS_DIR);
4365 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4366 options.sounds_directory,
4367 TREE_TYPE_SOUNDS_DIR);
4368 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4370 TREE_TYPE_SOUNDS_DIR);
4372 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4373 options.music_directory,
4374 TREE_TYPE_MUSIC_DIR);
4375 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4377 TREE_TYPE_MUSIC_DIR);
4379 if (artwork.gfx_first == NULL)
4380 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4381 if (artwork.snd_first == NULL)
4382 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4383 if (artwork.mus_first == NULL)
4384 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4386 // before sorting, the first entries will be from the user directory
4387 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4388 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4389 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4391 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4392 artwork.snd_current_identifier = artwork.snd_current->identifier;
4393 artwork.mus_current_identifier = artwork.mus_current->identifier;
4395 #if ENABLE_UNUSED_CODE
4396 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4397 artwork.gfx_current_identifier);
4398 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4399 artwork.snd_current_identifier);
4400 Debug("setup:LoadArtworkInfo", "music set == %s",
4401 artwork.mus_current_identifier);
4404 sortTreeInfo(&artwork.gfx_first);
4405 sortTreeInfo(&artwork.snd_first);
4406 sortTreeInfo(&artwork.mus_first);
4408 #if ENABLE_UNUSED_CODE
4409 dumpTreeInfo(artwork.gfx_first, 0);
4410 dumpTreeInfo(artwork.snd_first, 0);
4411 dumpTreeInfo(artwork.mus_first, 0);
4415 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4417 ArtworkDirTree *artwork_new = newTreeInfo();
4418 char *top_node_name = "standalone artwork";
4420 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4422 artwork_new->level_group = TRUE;
4424 setString(&artwork_new->identifier, top_node_name);
4425 setString(&artwork_new->name, top_node_name);
4426 setString(&artwork_new->name_sorting, top_node_name);
4428 // create node to link back to current custom artwork directory
4429 createParentTreeInfoNode(artwork_new);
4431 // move existing custom artwork tree into newly created sub-tree
4432 artwork_new->node_group->next = *artwork_node;
4434 // change custom artwork tree to contain only newly created node
4435 *artwork_node = artwork_new;
4438 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4439 ArtworkDirTree *node_parent,
4440 LevelDirTree *level_node,
4441 boolean empty_level_set_mode)
4443 int type = (*artwork_node)->type;
4445 // recursively check all level directories for artwork sub-directories
4449 boolean empty_level_set = (level_node->levels == 0);
4451 // check all tree entries for artwork, but skip parent link entries
4452 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4454 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4455 boolean cached = (artwork_new != NULL);
4459 pushTreeInfo(artwork_node, artwork_new);
4463 TreeInfo *topnode_last = *artwork_node;
4464 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4465 ARTWORK_DIRECTORY(type));
4467 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4469 if (topnode_last != *artwork_node) // check for newly added node
4471 artwork_new = *artwork_node;
4473 setString(&artwork_new->identifier, level_node->subdir);
4474 setString(&artwork_new->name, level_node->name);
4475 setString(&artwork_new->name_sorting, level_node->name_sorting);
4477 artwork_new->sort_priority = level_node->sort_priority;
4478 artwork_new->in_user_dir = level_node->in_user_dir;
4480 update_artworkinfo_cache = TRUE;
4486 // insert artwork info (from old cache or filesystem) into new cache
4487 if (artwork_new != NULL)
4488 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4491 DrawInitTextItem(level_node->name);
4493 if (level_node->node_group != NULL)
4495 TreeInfo *artwork_new = newTreeInfo();
4498 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4500 setTreeInfoToDefaults(artwork_new, type);
4502 artwork_new->level_group = TRUE;
4504 setString(&artwork_new->identifier, level_node->subdir);
4506 if (node_parent == NULL) // check for top tree node
4508 char *top_node_name = (empty_level_set_mode ?
4509 "artwork for certain level sets" :
4510 "artwork included in level sets");
4512 setString(&artwork_new->name, top_node_name);
4513 setString(&artwork_new->name_sorting, top_node_name);
4517 setString(&artwork_new->name, level_node->name);
4518 setString(&artwork_new->name_sorting, level_node->name_sorting);
4521 pushTreeInfo(artwork_node, artwork_new);
4523 // create node to link back to current custom artwork directory
4524 createParentTreeInfoNode(artwork_new);
4526 // recursively step into sub-directory and look for more custom artwork
4527 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4528 level_node->node_group,
4529 empty_level_set_mode);
4531 // if sub-tree has no custom artwork at all, remove it
4532 if (artwork_new->node_group->next == NULL)
4533 removeTreeInfo(artwork_node);
4536 level_node = level_node->next;
4540 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4542 // move peviously loaded artwork tree into separate sub-tree
4543 MoveArtworkInfoIntoSubTree(artwork_node);
4545 // load artwork from level sets into separate sub-trees
4546 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4547 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4549 // add top tree node over all sub-trees and set parent links
4550 *artwork_node = addTopTreeInfoNode(*artwork_node);
4553 void LoadLevelArtworkInfo(void)
4555 print_timestamp_init("LoadLevelArtworkInfo");
4557 DrawInitTextHead("Looking for custom level artwork");
4559 print_timestamp_time("DrawTimeText");
4561 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4562 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4563 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4564 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4565 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4566 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4568 SaveArtworkInfoCache();
4570 print_timestamp_time("SaveArtworkInfoCache");
4572 // needed for reloading level artwork not known at ealier stage
4573 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4574 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4575 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4577 print_timestamp_time("getTreeInfoFromIdentifier");
4579 sortTreeInfo(&artwork.gfx_first);
4580 sortTreeInfo(&artwork.snd_first);
4581 sortTreeInfo(&artwork.mus_first);
4583 print_timestamp_time("sortTreeInfo");
4585 #if ENABLE_UNUSED_CODE
4586 dumpTreeInfo(artwork.gfx_first, 0);
4587 dumpTreeInfo(artwork.snd_first, 0);
4588 dumpTreeInfo(artwork.mus_first, 0);
4591 print_timestamp_done("LoadLevelArtworkInfo");
4594 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4595 char *tree_subdir_new, int type)
4597 if (tree_node_old == NULL)
4599 if (type == TREE_TYPE_LEVEL_DIR)
4601 // get level info tree node of personal user level set
4602 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4604 // this may happen if "setup.internal.create_user_levelset" is FALSE
4605 // or if file "levelinfo.conf" is missing in personal user level set
4606 if (tree_node_old == NULL)
4607 tree_node_old = leveldir_first->node_group;
4611 // get artwork info tree node of first artwork set
4612 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4616 if (tree_dir == NULL)
4617 tree_dir = TREE_USERDIR(type);
4619 if (tree_node_old == NULL ||
4621 tree_subdir_new == NULL) // should not happen
4624 int draw_deactivation_mask = GetDrawDeactivationMask();
4626 // override draw deactivation mask (temporarily disable drawing)
4627 SetDrawDeactivationMask(REDRAW_ALL);
4629 if (type == TREE_TYPE_LEVEL_DIR)
4631 // load new level set config and add it next to first user level set
4632 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4633 tree_node_old->node_parent,
4634 tree_dir, tree_subdir_new);
4638 // load new artwork set config and add it next to first artwork set
4639 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4640 tree_node_old->node_parent,
4641 tree_dir, tree_subdir_new, type);
4644 // set draw deactivation mask to previous value
4645 SetDrawDeactivationMask(draw_deactivation_mask);
4647 // get first node of level or artwork info tree
4648 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4650 // get tree info node of newly added level or artwork set
4651 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4654 // if not found, check if added node is level group or artwork group
4655 if (tree_node_new == NULL)
4656 tree_node_new = getTreeInfoFromIdentifierExt(*tree_node_first,
4658 TREE_NODE_TYPE_GROUP);
4660 if (tree_node_new == NULL) // should not happen
4663 // correct top link and parent node link of newly created tree node
4664 tree_node_new->node_top = tree_node_old->node_top;
4665 tree_node_new->node_parent = tree_node_old->node_parent;
4667 // sort tree info to adjust position of newly added tree set
4668 sortTreeInfo(tree_node_first);
4673 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4674 char *tree_subdir_new, int type)
4676 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4677 Fail("internal tree info structure corrupted -- aborting");
4680 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4682 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4685 char *getArtworkIdentifierForUserLevelSet(int type)
4687 char *classic_artwork_set = getClassicArtworkSet(type);
4689 // check for custom artwork configured in "levelinfo.conf"
4690 char *leveldir_artwork_set =
4691 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4692 boolean has_leveldir_artwork_set =
4693 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4694 classic_artwork_set));
4696 // check for custom artwork in sub-directory "graphics" etc.
4697 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4698 char *leveldir_identifier = leveldir_current->identifier;
4699 boolean has_artwork_subdir =
4700 (getTreeInfoFromIdentifier(artwork_first_node,
4701 leveldir_identifier) != NULL);
4703 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4704 has_artwork_subdir ? leveldir_identifier :
4705 classic_artwork_set);
4708 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4710 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4711 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4712 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4716 ti = getTreeInfoFromIdentifier(artwork_first_node,
4717 ARTWORK_DEFAULT_SUBDIR(type));
4719 Fail("cannot find default graphics -- should not happen");
4725 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4727 char *graphics_set =
4728 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4730 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4732 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4734 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4735 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4736 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4739 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4740 char *level_author, int num_levels)
4742 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4743 char *filename_tmp = getStringCat2(filename, ".tmp");
4745 FILE *file_tmp = NULL;
4746 char line[MAX_LINE_LEN];
4747 boolean success = FALSE;
4748 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4750 // update values in level directory tree
4752 if (level_name != NULL)
4753 setString(&leveldir->name, level_name);
4755 if (level_author != NULL)
4756 setString(&leveldir->author, level_author);
4758 if (num_levels != -1)
4759 leveldir->levels = num_levels;
4761 // update values that depend on other values
4763 setString(&leveldir->name_sorting, leveldir->name);
4765 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4767 // sort order of level sets may have changed
4768 sortTreeInfo(&leveldir_first);
4770 if ((file = fopen(filename, MODE_READ)) &&
4771 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4773 while (fgets(line, MAX_LINE_LEN, file))
4775 if (strPrefix(line, "name:") && level_name != NULL)
4776 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4777 else if (strPrefix(line, "author:") && level_author != NULL)
4778 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4779 else if (strPrefix(line, "levels:") && num_levels != -1)
4780 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4782 fputs(line, file_tmp);
4795 success = (rename(filename_tmp, filename) == 0);
4803 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4804 char *level_author, int num_levels,
4805 boolean use_artwork_set)
4807 LevelDirTree *level_info;
4812 // create user level sub-directory, if needed
4813 createDirectory(getUserLevelDir(level_subdir), "user level");
4815 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4817 if (!(file = fopen(filename, MODE_WRITE)))
4819 Warn("cannot write level info file '%s'", filename);
4826 level_info = newTreeInfo();
4828 // always start with reliable default values
4829 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4831 setString(&level_info->name, level_name);
4832 setString(&level_info->author, level_author);
4833 level_info->levels = num_levels;
4834 level_info->first_level = 1;
4835 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4836 level_info->readonly = FALSE;
4838 if (use_artwork_set)
4840 level_info->graphics_set =
4841 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4842 level_info->sounds_set =
4843 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4844 level_info->music_set =
4845 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4848 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4850 fprintFileHeader(file, LEVELINFO_FILENAME);
4853 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4855 if (i == LEVELINFO_TOKEN_NAME ||
4856 i == LEVELINFO_TOKEN_AUTHOR ||
4857 i == LEVELINFO_TOKEN_LEVELS ||
4858 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4859 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4860 i == LEVELINFO_TOKEN_READONLY ||
4861 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4862 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4863 i == LEVELINFO_TOKEN_MUSIC_SET)))
4864 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4866 // just to make things nicer :)
4867 if (i == LEVELINFO_TOKEN_AUTHOR ||
4868 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4869 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4870 fprintf(file, "\n");
4873 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4877 SetFilePermissions(filename, PERMS_PRIVATE);
4879 freeTreeInfo(level_info);
4885 static void SaveUserLevelInfo(void)
4887 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4890 char *getSetupValue(int type, void *value)
4892 static char value_string[MAX_LINE_LEN];
4900 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4904 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4907 case TYPE_SWITCH_3_STATES:
4908 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4909 *(int *)value == STATE_ASK ? "ask" :
4910 *(int *)value == STATE_FALSE ? "off" : "on"));
4914 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4917 case TYPE_YES_NO_AUTO:
4918 strcpy(value_string, (*(int *)value == STATE_AUTO ? "auto" :
4919 *(int *)value == STATE_FALSE ? "no" : "yes"));
4922 case TYPE_YES_NO_ASK:
4923 strcpy(value_string, (*(int *)value == STATE_ASK ? "ask" :
4924 *(int *)value == STATE_FALSE ? "no" : "yes"));
4928 strcpy(value_string, (*(boolean *)value ? "new" : "old"));
4932 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4936 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4940 sprintf(value_string, "%d", *(int *)value);
4944 if (*(char **)value == NULL)
4947 strcpy(value_string, *(char **)value);
4951 sprintf(value_string, "player_%d", *(int *)value + 1);
4955 value_string[0] = '\0';
4959 if (type & TYPE_GHOSTED)
4960 strcpy(value_string, "n/a");
4962 return value_string;
4965 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4969 static char token_string[MAX_LINE_LEN];
4970 int token_type = token_info[token_nr].type;
4971 void *setup_value = token_info[token_nr].value;
4972 char *token_text = token_info[token_nr].text;
4973 char *value_string = getSetupValue(token_type, setup_value);
4975 // build complete token string
4976 sprintf(token_string, "%s%s", prefix, token_text);
4978 // build setup entry line
4979 line = getFormattedSetupEntry(token_string, value_string);
4981 if (token_type == TYPE_KEY_X11)
4983 Key key = *(Key *)setup_value;
4984 char *keyname = getKeyNameFromKey(key);
4986 // add comment, if useful
4987 if (!strEqual(keyname, "(undefined)") &&
4988 !strEqual(keyname, "(unknown)"))
4990 // add at least one whitespace
4992 for (i = strlen(line); i < token_comment_position; i++)
4996 strcat(line, keyname);
5003 static void InitLastPlayedLevels_ParentNode(void)
5005 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
5006 LevelDirTree *leveldir_new = NULL;
5008 // check if parent node for last played levels already exists
5009 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
5012 leveldir_new = newTreeInfo();
5014 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
5016 leveldir_new->level_group = TRUE;
5017 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
5019 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
5020 setString(&leveldir_new->name, "<< (last played level sets)");
5021 setString(&leveldir_new->name_sorting, leveldir_new->name);
5023 pushTreeInfo(leveldir_top, leveldir_new);
5025 // create node to link back to current level directory
5026 createParentTreeInfoNode(leveldir_new);
5029 void UpdateLastPlayedLevels_TreeInfo(void)
5031 char **last_level_series = setup.level_setup.last_level_series;
5032 LevelDirTree *leveldir_last;
5033 TreeInfo **node_new = NULL;
5036 if (last_level_series[0] == NULL)
5039 InitLastPlayedLevels_ParentNode();
5041 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
5042 TOKEN_STR_LAST_LEVEL_SERIES,
5043 TREE_NODE_TYPE_GROUP);
5044 if (leveldir_last == NULL)
5047 node_new = &leveldir_last->node_group->next;
5049 freeTreeInfo(*node_new);
5053 for (i = 0; last_level_series[i] != NULL; i++)
5055 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
5056 last_level_series[i]);
5057 if (node_last == NULL)
5060 *node_new = getTreeInfoCopy(node_last); // copy complete node
5062 (*node_new)->node_top = &leveldir_first; // correct top node link
5063 (*node_new)->node_parent = leveldir_last; // correct parent node link
5065 (*node_new)->is_copy = TRUE; // mark entry as node copy
5067 (*node_new)->node_group = NULL;
5068 (*node_new)->next = NULL;
5070 (*node_new)->cl_first = -1; // force setting tree cursor
5072 node_new = &((*node_new)->next);
5076 static void UpdateLastPlayedLevels_List(void)
5078 char **last_level_series = setup.level_setup.last_level_series;
5079 int pos = MAX_LEVELDIR_HISTORY - 1;
5082 // search for potentially already existing entry in list of level sets
5083 for (i = 0; last_level_series[i] != NULL; i++)
5084 if (strEqual(last_level_series[i], leveldir_current->identifier))
5087 // move list of level sets one entry down (using potentially free entry)
5088 for (i = pos; i > 0; i--)
5089 setString(&last_level_series[i], last_level_series[i - 1]);
5091 // put last played level set at top position
5092 setString(&last_level_series[0], leveldir_current->identifier);
5095 #define LAST_PLAYED_MODE_SET 1
5096 #define LAST_PLAYED_MODE_SET_FORCED 2
5097 #define LAST_PLAYED_MODE_GET 3
5099 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, int mode)
5101 static char *identifier = NULL;
5103 if (mode == LAST_PLAYED_MODE_SET)
5105 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
5107 else if (mode == LAST_PLAYED_MODE_SET_FORCED)
5109 setString(&identifier, (node ? node->identifier : NULL));
5111 else if (mode == LAST_PLAYED_MODE_GET)
5113 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
5115 TREE_NODE_TYPE_COPY);
5116 return (node_new != NULL ? node_new : node);
5119 return NULL; // not used
5122 void StoreLastPlayedLevels(TreeInfo *node)
5124 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET);
5127 void ForcedStoreLastPlayedLevels(TreeInfo *node)
5129 StoreOrRestoreLastPlayedLevels(node, LAST_PLAYED_MODE_SET_FORCED);
5132 void RestoreLastPlayedLevels(TreeInfo **node)
5134 *node = StoreOrRestoreLastPlayedLevels(*node, LAST_PLAYED_MODE_GET);
5137 boolean CheckLastPlayedLevels(void)
5139 return (StoreOrRestoreLastPlayedLevels(NULL, LAST_PLAYED_MODE_GET) != NULL);
5142 void LoadLevelSetup_LastSeries(void)
5144 // --------------------------------------------------------------------------
5145 // ~/.<program>/levelsetup.conf
5146 // --------------------------------------------------------------------------
5148 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5149 SetupFileHash *level_setup_hash = NULL;
5153 // always start with reliable default values
5154 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5156 // start with empty history of last played level sets
5157 setString(&setup.level_setup.last_level_series[0], NULL);
5159 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
5161 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5163 if (leveldir_current == NULL)
5164 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5167 if ((level_setup_hash = loadSetupFileHash(filename)))
5169 char *last_level_series =
5170 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
5172 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
5174 if (leveldir_current == NULL)
5175 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
5177 char *last_played_menu_used =
5178 getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_MENU_USED);
5180 // store if last level set was selected from "last played" menu
5181 if (strEqual(last_played_menu_used, "true"))
5182 ForcedStoreLastPlayedLevels(leveldir_current);
5184 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
5186 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
5187 LevelDirTree *leveldir_last;
5189 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5191 last_level_series = getHashEntry(level_setup_hash, token);
5193 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
5195 if (leveldir_last != NULL)
5196 setString(&setup.level_setup.last_level_series[pos++],
5200 setString(&setup.level_setup.last_level_series[pos], NULL);
5202 freeSetupFileHash(level_setup_hash);
5206 Debug("setup", "using default setup values");
5212 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5214 // --------------------------------------------------------------------------
5215 // ~/.<program>/levelsetup.conf
5216 // --------------------------------------------------------------------------
5218 // check if the current level directory structure is available at this point
5219 if (leveldir_current == NULL)
5222 char **last_level_series = setup.level_setup.last_level_series;
5223 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5227 InitUserDataDirectory();
5229 UpdateLastPlayedLevels_List();
5231 if (!(file = fopen(filename, MODE_WRITE)))
5233 Warn("cannot write setup file '%s'", filename);
5240 fprintFileHeader(file, LEVELSETUP_FILENAME);
5242 if (deactivate_last_level_series)
5243 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5245 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5246 leveldir_current->identifier));
5248 // store if last level set was selected from "last played" menu
5249 boolean last_played_menu_used = CheckLastPlayedLevels();
5250 char *setup_value = getSetupValue(TYPE_BOOLEAN, &last_played_menu_used);
5252 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_MENU_USED,
5255 for (i = 0; last_level_series[i] != NULL; i++)
5257 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5259 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5261 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5266 SetFilePermissions(filename, PERMS_PRIVATE);
5271 void SaveLevelSetup_LastSeries(void)
5273 SaveLevelSetup_LastSeries_Ext(FALSE);
5276 void SaveLevelSetup_LastSeries_Deactivate(void)
5278 SaveLevelSetup_LastSeries_Ext(TRUE);
5281 static void checkSeriesInfo(void)
5283 static char *level_directory = NULL;
5286 DirectoryEntry *dir_entry;
5289 checked_free(level_directory);
5291 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5293 level_directory = getPath2((leveldir_current->in_user_dir ?
5294 getUserLevelDir(NULL) :
5295 options.level_directory),
5296 leveldir_current->fullpath);
5298 if ((dir = openDirectory(level_directory)) == NULL)
5300 Warn("cannot read level directory '%s'", level_directory);
5306 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5308 if (strlen(dir_entry->basename) > 4 &&
5309 dir_entry->basename[3] == '.' &&
5310 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5312 char levelnum_str[4];
5315 strncpy(levelnum_str, dir_entry->basename, 3);
5316 levelnum_str[3] = '\0';
5318 levelnum_value = atoi(levelnum_str);
5320 if (levelnum_value < leveldir_current->first_level)
5322 Warn("additional level %d found", levelnum_value);
5324 leveldir_current->first_level = levelnum_value;
5326 else if (levelnum_value > leveldir_current->last_level)
5328 Warn("additional level %d found", levelnum_value);
5330 leveldir_current->last_level = levelnum_value;
5336 closeDirectory(dir);
5339 void LoadLevelSetup_SeriesInfo(void)
5342 SetupFileHash *level_setup_hash = NULL;
5343 char *level_subdir = leveldir_current->subdir;
5346 // always start with reliable default values
5347 level_nr = leveldir_current->first_level;
5349 for (i = 0; i < MAX_LEVELS; i++)
5351 LevelStats_setPlayed(i, 0);
5352 LevelStats_setSolved(i, 0);
5357 // --------------------------------------------------------------------------
5358 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5359 // --------------------------------------------------------------------------
5361 level_subdir = leveldir_current->subdir;
5363 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5365 if ((level_setup_hash = loadSetupFileHash(filename)))
5369 // get last played level in this level set
5371 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5375 level_nr = atoi(token_value);
5377 if (level_nr < leveldir_current->first_level)
5378 level_nr = leveldir_current->first_level;
5379 if (level_nr > leveldir_current->last_level)
5380 level_nr = leveldir_current->last_level;
5383 // get handicap level in this level set
5385 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5389 int level_nr = atoi(token_value);
5391 if (level_nr < leveldir_current->first_level)
5392 level_nr = leveldir_current->first_level;
5393 if (level_nr > leveldir_current->last_level + 1)
5394 level_nr = leveldir_current->last_level;
5396 if (leveldir_current->user_defined || !leveldir_current->handicap)
5397 level_nr = leveldir_current->last_level;
5399 leveldir_current->handicap_level = level_nr;
5402 // get number of played and solved levels in this level set
5404 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5406 char *token = HASH_ITERATION_TOKEN(itr);
5407 char *value = HASH_ITERATION_VALUE(itr);
5409 if (strlen(token) == 3 &&
5410 token[0] >= '0' && token[0] <= '9' &&
5411 token[1] >= '0' && token[1] <= '9' &&
5412 token[2] >= '0' && token[2] <= '9')
5414 int level_nr = atoi(token);
5417 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5419 value = strchr(value, ' ');
5422 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5425 END_HASH_ITERATION(hash, itr)
5427 freeSetupFileHash(level_setup_hash);
5431 Debug("setup", "using default setup values");
5437 void SaveLevelSetup_SeriesInfo(void)
5440 char *level_subdir = leveldir_current->subdir;
5441 char *level_nr_str = int2str(level_nr, 0);
5442 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5446 // --------------------------------------------------------------------------
5447 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5448 // --------------------------------------------------------------------------
5450 InitLevelSetupDirectory(level_subdir);
5452 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5454 if (!(file = fopen(filename, MODE_WRITE)))
5456 Warn("cannot write setup file '%s'", filename);
5463 fprintFileHeader(file, LEVELSETUP_FILENAME);
5465 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5467 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5468 handicap_level_str));
5470 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5473 if (LevelStats_getPlayed(i) > 0 ||
5474 LevelStats_getSolved(i) > 0)
5479 sprintf(token, "%03d", i);
5480 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5482 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5488 SetFilePermissions(filename, PERMS_PRIVATE);
5493 int LevelStats_getPlayed(int nr)
5495 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5498 int LevelStats_getSolved(int nr)
5500 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5503 void LevelStats_setPlayed(int nr, int value)
5505 if (nr >= 0 && nr < MAX_LEVELS)
5506 level_stats[nr].played = value;
5509 void LevelStats_setSolved(int nr, int value)
5511 if (nr >= 0 && nr < MAX_LEVELS)
5512 level_stats[nr].solved = value;
5515 void LevelStats_incPlayed(int nr)
5517 if (nr >= 0 && nr < MAX_LEVELS)
5518 level_stats[nr].played++;
5521 void LevelStats_incSolved(int nr)
5523 if (nr >= 0 && nr < MAX_LEVELS)
5524 level_stats[nr].solved++;
5527 void LoadUserSetup(void)
5529 // --------------------------------------------------------------------------
5530 // ~/.<program>/usersetup.conf
5531 // --------------------------------------------------------------------------
5533 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5534 SetupFileHash *user_setup_hash = NULL;
5536 // always start with reliable default values
5539 if ((user_setup_hash = loadSetupFileHash(filename)))
5543 // get last selected user number
5544 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5547 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5549 freeSetupFileHash(user_setup_hash);
5553 Debug("setup", "using default setup values");
5559 void SaveUserSetup(void)
5561 // --------------------------------------------------------------------------
5562 // ~/.<program>/usersetup.conf
5563 // --------------------------------------------------------------------------
5565 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5568 InitMainUserDataDirectory();
5570 if (!(file = fopen(filename, MODE_WRITE)))
5572 Warn("cannot write setup file '%s'", filename);
5579 fprintFileHeader(file, USERSETUP_FILENAME);
5581 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5585 SetFilePermissions(filename, PERMS_PRIVATE);