1 // ============================================================================
2 // Artsoft Retro-Game Library
3 // ----------------------------------------------------------------------------
4 // (c) 1995-2014 by Artsoft Entertainment
7 // https://www.artsoft.org/
8 // ----------------------------------------------------------------------------
10 // ============================================================================
12 #include <sys/types.h>
26 #include "zip/miniunz.h"
29 #define ENABLE_UNUSED_CODE FALSE // for currently unused functions
30 #define DEBUG_NO_CONFIG_FILE FALSE // for extra-verbose debug output
32 #define NUM_LEVELCLASS_DESC 8
34 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
46 #define TOKEN_VALUE_POSITION_SHORT 32
47 #define TOKEN_VALUE_POSITION_DEFAULT 40
48 #define TOKEN_COMMENT_POSITION_DEFAULT 60
50 #define TREE_NODE_TYPE_DEFAULT 0
51 #define TREE_NODE_TYPE_PARENT 1
52 #define TREE_NODE_TYPE_GROUP 2
53 #define TREE_NODE_TYPE_COPY 3
55 #define TREE_NODE_TYPE(ti) (ti->node_group ? TREE_NODE_TYPE_GROUP : \
56 ti->parent_link ? TREE_NODE_TYPE_PARENT : \
57 ti->is_copy ? TREE_NODE_TYPE_COPY : \
58 TREE_NODE_TYPE_DEFAULT)
61 static void setTreeInfoToDefaults(TreeInfo *, int);
62 static TreeInfo *getTreeInfoCopy(TreeInfo *ti);
63 static int compareTreeInfoEntries(const void *, const void *);
65 static int token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
66 static int token_comment_position = TOKEN_COMMENT_POSITION_DEFAULT;
68 static SetupFileHash *artworkinfo_cache_old = NULL;
69 static SetupFileHash *artworkinfo_cache_new = NULL;
70 static SetupFileHash *optional_tokens_hash = NULL;
71 static SetupFileHash *missing_file_hash = NULL;
72 static boolean use_artworkinfo_cache = TRUE;
73 static boolean update_artworkinfo_cache = FALSE;
76 // ----------------------------------------------------------------------------
78 // ----------------------------------------------------------------------------
80 static void WarnUsingFallback(char *filename)
82 if (getHashEntry(missing_file_hash, filename) == NULL)
84 setHashEntry(missing_file_hash, filename, "");
86 Debug("setup", "cannot find artwork file '%s' (using fallback)", filename);
90 static char *getLevelClassDescription(TreeInfo *ti)
92 int position = ti->sort_priority / 100;
94 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
95 return levelclass_desc[position];
97 return "Unknown Level Class";
100 static char *getCacheDir(void)
102 static char *cache_dir = NULL;
104 if (cache_dir == NULL)
105 cache_dir = getPath2(getMainUserGameDataDir(), CACHE_DIRECTORY);
110 static char *getScoreDir(char *level_subdir)
112 static char *score_dir = NULL;
113 static char *score_level_dir = NULL;
114 char *score_subdir = SCORES_DIRECTORY;
116 if (score_dir == NULL)
117 score_dir = getPath2(getMainUserGameDataDir(), score_subdir);
119 if (level_subdir != NULL)
121 checked_free(score_level_dir);
123 score_level_dir = getPath2(score_dir, level_subdir);
125 return score_level_dir;
131 static char *getScoreCacheDir(char *level_subdir)
133 static char *score_dir = NULL;
134 static char *score_level_dir = NULL;
135 char *score_subdir = SCORES_DIRECTORY;
137 if (score_dir == NULL)
138 score_dir = getPath2(getCacheDir(), score_subdir);
140 if (level_subdir != NULL)
142 checked_free(score_level_dir);
144 score_level_dir = getPath2(score_dir, level_subdir);
146 return score_level_dir;
152 static char *getScoreTapeDir(char *level_subdir, int nr)
154 static char *score_tape_dir = NULL;
155 char tape_subdir[MAX_FILENAME_LEN];
157 checked_free(score_tape_dir);
159 sprintf(tape_subdir, "%03d", nr);
160 score_tape_dir = getPath2(getScoreDir(level_subdir), tape_subdir);
162 return score_tape_dir;
165 static char *getScoreCacheTapeDir(char *level_subdir, int nr)
167 static char *score_cache_tape_dir = NULL;
168 char tape_subdir[MAX_FILENAME_LEN];
170 checked_free(score_cache_tape_dir);
172 sprintf(tape_subdir, "%03d", nr);
173 score_cache_tape_dir = getPath2(getScoreCacheDir(level_subdir), tape_subdir);
175 return score_cache_tape_dir;
178 static char *getUserSubdir(int nr)
180 static char user_subdir[16] = { 0 };
182 sprintf(user_subdir, "%03d", nr);
187 static char *getUserDir(int nr)
189 static char *user_dir = NULL;
190 char *main_data_dir = getMainUserGameDataDir();
191 char *users_subdir = USERS_DIRECTORY;
192 char *user_subdir = getUserSubdir(nr);
194 checked_free(user_dir);
197 user_dir = getPath3(main_data_dir, users_subdir, user_subdir);
199 user_dir = getPath2(main_data_dir, users_subdir);
204 static char *getLevelSetupDir(char *level_subdir)
206 static char *levelsetup_dir = NULL;
207 char *data_dir = getUserGameDataDir();
208 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
210 checked_free(levelsetup_dir);
212 if (level_subdir != NULL)
213 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
215 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
217 return levelsetup_dir;
220 static char *getNetworkDir(void)
222 static char *network_dir = NULL;
224 if (network_dir == NULL)
225 network_dir = getPath2(getMainUserGameDataDir(), NETWORK_DIRECTORY);
230 char *getLevelDirFromTreeInfo(TreeInfo *node)
232 static char *level_dir = NULL;
235 return options.level_directory;
237 checked_free(level_dir);
239 level_dir = getPath2((node->in_user_dir ? getUserLevelDir(NULL) :
240 options.level_directory), node->fullpath);
245 char *getUserLevelDir(char *level_subdir)
247 static char *userlevel_dir = NULL;
248 char *data_dir = getMainUserGameDataDir();
249 char *userlevel_subdir = LEVELS_DIRECTORY;
251 checked_free(userlevel_dir);
253 if (level_subdir != NULL)
254 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
256 userlevel_dir = getPath2(data_dir, userlevel_subdir);
258 return userlevel_dir;
261 char *getNetworkLevelDir(char *level_subdir)
263 static char *network_level_dir = NULL;
264 char *data_dir = getNetworkDir();
265 char *networklevel_subdir = LEVELS_DIRECTORY;
267 checked_free(network_level_dir);
269 if (level_subdir != NULL)
270 network_level_dir = getPath3(data_dir, networklevel_subdir, level_subdir);
272 network_level_dir = getPath2(data_dir, networklevel_subdir);
274 return network_level_dir;
277 char *getCurrentLevelDir(void)
279 return getLevelDirFromTreeInfo(leveldir_current);
282 char *getNewUserLevelSubdir(void)
284 static char *new_level_subdir = NULL;
285 char *subdir_prefix = getLoginName();
286 char subdir_suffix[10];
287 int max_suffix_number = 1000;
290 while (++i < max_suffix_number)
292 sprintf(subdir_suffix, "_%d", i);
294 checked_free(new_level_subdir);
295 new_level_subdir = getStringCat2(subdir_prefix, subdir_suffix);
297 if (!directoryExists(getUserLevelDir(new_level_subdir)))
301 return new_level_subdir;
304 char *getTapeDir(char *level_subdir)
306 static char *tape_dir = NULL;
307 char *data_dir = getUserGameDataDir();
308 char *tape_subdir = TAPES_DIRECTORY;
310 checked_free(tape_dir);
312 if (level_subdir != NULL)
313 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
315 tape_dir = getPath2(data_dir, tape_subdir);
320 static char *getSolutionTapeDir(void)
322 static char *tape_dir = NULL;
323 char *data_dir = getCurrentLevelDir();
324 char *tape_subdir = TAPES_DIRECTORY;
326 checked_free(tape_dir);
328 tape_dir = getPath2(data_dir, tape_subdir);
333 static char *getDefaultGraphicsDir(char *graphics_subdir)
335 static char *graphics_dir = NULL;
337 if (graphics_subdir == NULL)
338 return options.graphics_directory;
340 checked_free(graphics_dir);
342 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
347 static char *getDefaultSoundsDir(char *sounds_subdir)
349 static char *sounds_dir = NULL;
351 if (sounds_subdir == NULL)
352 return options.sounds_directory;
354 checked_free(sounds_dir);
356 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
361 static char *getDefaultMusicDir(char *music_subdir)
363 static char *music_dir = NULL;
365 if (music_subdir == NULL)
366 return options.music_directory;
368 checked_free(music_dir);
370 music_dir = getPath2(options.music_directory, music_subdir);
375 static char *getClassicArtworkSet(int type)
377 return (type == TREE_TYPE_GRAPHICS_DIR ? GFX_CLASSIC_SUBDIR :
378 type == TREE_TYPE_SOUNDS_DIR ? SND_CLASSIC_SUBDIR :
379 type == TREE_TYPE_MUSIC_DIR ? MUS_CLASSIC_SUBDIR : "");
382 static char *getClassicArtworkDir(int type)
384 return (type == TREE_TYPE_GRAPHICS_DIR ?
385 getDefaultGraphicsDir(GFX_CLASSIC_SUBDIR) :
386 type == TREE_TYPE_SOUNDS_DIR ?
387 getDefaultSoundsDir(SND_CLASSIC_SUBDIR) :
388 type == TREE_TYPE_MUSIC_DIR ?
389 getDefaultMusicDir(MUS_CLASSIC_SUBDIR) : "");
392 char *getUserGraphicsDir(void)
394 static char *usergraphics_dir = NULL;
396 if (usergraphics_dir == NULL)
397 usergraphics_dir = getPath2(getMainUserGameDataDir(), GRAPHICS_DIRECTORY);
399 return usergraphics_dir;
402 char *getUserSoundsDir(void)
404 static char *usersounds_dir = NULL;
406 if (usersounds_dir == NULL)
407 usersounds_dir = getPath2(getMainUserGameDataDir(), SOUNDS_DIRECTORY);
409 return usersounds_dir;
412 char *getUserMusicDir(void)
414 static char *usermusic_dir = NULL;
416 if (usermusic_dir == NULL)
417 usermusic_dir = getPath2(getMainUserGameDataDir(), MUSIC_DIRECTORY);
419 return usermusic_dir;
422 static char *getSetupArtworkDir(TreeInfo *ti)
424 static char *artwork_dir = NULL;
429 checked_free(artwork_dir);
431 artwork_dir = getPath2(ti->basepath, ti->fullpath);
436 char *setLevelArtworkDir(TreeInfo *ti)
438 char **artwork_path_ptr, **artwork_set_ptr;
439 TreeInfo *level_artwork;
441 if (ti == NULL || leveldir_current == NULL)
444 artwork_path_ptr = LEVELDIR_ARTWORK_PATH_PTR(leveldir_current, ti->type);
445 artwork_set_ptr = LEVELDIR_ARTWORK_SET_PTR( leveldir_current, ti->type);
447 checked_free(*artwork_path_ptr);
449 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
451 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
456 No (or non-existing) artwork configured in "levelinfo.conf". This would
457 normally result in using the artwork configured in the setup menu. But
458 if an artwork subdirectory exists (which might contain custom artwork
459 or an artwork configuration file), this level artwork must be treated
460 as relative to the default "classic" artwork, not to the artwork that
461 is currently configured in the setup menu.
463 Update: For "special" versions of R'n'D (like "R'n'D jue"), do not use
464 the "default" artwork (which would be "jue0" for "R'n'D jue"), but use
465 the real "classic" artwork from the original R'n'D (like "gfx_classic").
468 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
470 checked_free(*artwork_set_ptr);
472 if (directoryExists(dir))
474 *artwork_path_ptr = getStringCopy(getClassicArtworkDir(ti->type));
475 *artwork_set_ptr = getStringCopy(getClassicArtworkSet(ti->type));
479 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
480 *artwork_set_ptr = NULL;
486 return *artwork_set_ptr;
489 static char *getLevelArtworkSet(int type)
491 if (leveldir_current == NULL)
494 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
497 static char *getLevelArtworkDir(int type)
499 if (leveldir_current == NULL)
500 return UNDEFINED_FILENAME;
502 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
505 char *getProgramMainDataPath(char *command_filename, char *base_path)
507 // check if the program's main data base directory is configured
508 if (!strEqual(base_path, "."))
509 return getStringCopy(base_path);
511 /* if the program is configured to start from current directory (default),
512 determine program package directory from program binary (some versions
513 of KDE/Konqueror and Mac OS X (especially "Mavericks") apparently do not
514 set the current working directory to the program package directory) */
515 char *main_data_path = getBasePath(command_filename);
517 #if defined(PLATFORM_MAC)
518 if (strSuffix(main_data_path, MAC_APP_BINARY_SUBDIR))
520 char *main_data_path_old = main_data_path;
522 // cut relative path to Mac OS X application binary directory from path
523 main_data_path[strlen(main_data_path) -
524 strlen(MAC_APP_BINARY_SUBDIR)] = '\0';
526 // cut trailing path separator from path (but not if path is root directory)
527 if (strSuffix(main_data_path, "/") && !strEqual(main_data_path, "/"))
528 main_data_path[strlen(main_data_path) - 1] = '\0';
530 // replace empty path with current directory
531 if (strEqual(main_data_path, ""))
532 main_data_path = ".";
534 // add relative path to Mac OS X application resources directory to path
535 main_data_path = getPath2(main_data_path, MAC_APP_FILES_SUBDIR);
537 free(main_data_path_old);
541 return main_data_path;
544 char *getProgramConfigFilename(char *command_filename)
546 static char *config_filename_1 = NULL;
547 static char *config_filename_2 = NULL;
548 static char *config_filename_3 = NULL;
549 static boolean initialized = FALSE;
553 char *command_filename_1 = getStringCopy(command_filename);
555 // strip trailing executable suffix from command filename
556 if (strSuffix(command_filename_1, ".exe"))
557 command_filename_1[strlen(command_filename_1) - 4] = '\0';
559 char *base_path = getProgramMainDataPath(command_filename, BASE_PATH);
560 char *conf_directory = getPath2(base_path, CONF_DIRECTORY);
562 char *command_basepath = getBasePath(command_filename);
563 char *command_basename = getBaseNameNoSuffix(command_filename);
564 char *command_filename_2 = getPath2(command_basepath, command_basename);
566 config_filename_1 = getStringCat2(command_filename_1, ".conf");
567 config_filename_2 = getStringCat2(command_filename_2, ".conf");
568 config_filename_3 = getPath2(conf_directory, SETUP_FILENAME);
570 checked_free(base_path);
571 checked_free(conf_directory);
573 checked_free(command_basepath);
574 checked_free(command_basename);
576 checked_free(command_filename_1);
577 checked_free(command_filename_2);
582 // 1st try: look for config file that exactly matches the binary filename
583 if (fileExists(config_filename_1))
584 return config_filename_1;
586 // 2nd try: look for config file that matches binary filename without suffix
587 if (fileExists(config_filename_2))
588 return config_filename_2;
590 // 3rd try: return setup config filename in global program config directory
591 return config_filename_3;
594 static char *getPlatformConfigFilename(char *config_filename)
596 static char *platform_config_filename = NULL;
597 static boolean initialized = FALSE;
601 char *config_basepath = getBasePath(config_filename);
602 char *config_basename = getBaseNameNoSuffix(config_filename);
603 char *config_filename_prefix = getPath2(config_basepath, config_basename);
604 char *platform_string_lower = getStringToLower(PLATFORM_STRING);
605 char *platform_suffix = getStringCat2("-", platform_string_lower);
607 platform_config_filename = getStringCat3(config_filename_prefix,
608 platform_suffix, ".conf");
610 checked_free(config_basepath);
611 checked_free(config_basename);
612 checked_free(config_filename_prefix);
613 checked_free(platform_string_lower);
614 checked_free(platform_suffix);
619 return platform_config_filename;
622 char *getTapeFilename(int nr)
624 static char *filename = NULL;
625 char basename[MAX_FILENAME_LEN];
627 checked_free(filename);
629 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
630 filename = getPath2(getTapeDir(leveldir_current->subdir), basename);
635 char *getTemporaryTapeFilename(void)
637 static char *filename = NULL;
638 char basename[MAX_FILENAME_LEN];
640 checked_free(filename);
642 sprintf(basename, "tmp.%s", TAPEFILE_EXTENSION);
643 filename = getPath2(getTapeDir(NULL), basename);
648 char *getDefaultSolutionTapeFilename(int nr)
650 static char *filename = NULL;
651 char basename[MAX_FILENAME_LEN];
653 checked_free(filename);
655 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
656 filename = getPath2(getSolutionTapeDir(), basename);
661 char *getSokobanSolutionTapeFilename(int nr)
663 static char *filename = NULL;
664 char basename[MAX_FILENAME_LEN];
666 checked_free(filename);
668 sprintf(basename, "%03d.sln", nr);
669 filename = getPath2(getSolutionTapeDir(), basename);
674 char *getSolutionTapeFilename(int nr)
676 char *filename = getDefaultSolutionTapeFilename(nr);
678 if (!fileExists(filename))
680 char *filename2 = getSokobanSolutionTapeFilename(nr);
682 if (fileExists(filename2))
689 char *getScoreFilename(int nr)
691 static char *filename = NULL;
692 char basename[MAX_FILENAME_LEN];
694 checked_free(filename);
696 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
698 // used instead of "leveldir_current->subdir" (for network games)
699 filename = getPath2(getScoreDir(levelset.identifier), basename);
704 char *getScoreCacheFilename(int nr)
706 static char *filename = NULL;
707 char basename[MAX_FILENAME_LEN];
709 checked_free(filename);
711 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
713 // used instead of "leveldir_current->subdir" (for network games)
714 filename = getPath2(getScoreCacheDir(levelset.identifier), basename);
719 char *getScoreTapeBasename(char *name)
721 static char basename[MAX_FILENAME_LEN];
722 char basename_raw[MAX_FILENAME_LEN];
725 sprintf(timestamp, "%s", getCurrentTimestamp());
726 sprintf(basename_raw, "%s-%s", timestamp, name);
727 sprintf(basename, "%s-%08x", timestamp, get_hash_from_key(basename_raw));
732 char *getScoreTapeFilename(char *basename_no_ext, int nr)
734 static char *filename = NULL;
735 char basename[MAX_FILENAME_LEN];
737 checked_free(filename);
739 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
741 // used instead of "leveldir_current->subdir" (for network games)
742 filename = getPath2(getScoreTapeDir(levelset.identifier, nr), basename);
747 char *getScoreCacheTapeFilename(char *basename_no_ext, int nr)
749 static char *filename = NULL;
750 char basename[MAX_FILENAME_LEN];
752 checked_free(filename);
754 sprintf(basename, "%s.%s", basename_no_ext, TAPEFILE_EXTENSION);
756 // used instead of "leveldir_current->subdir" (for network games)
757 filename = getPath2(getScoreCacheTapeDir(levelset.identifier, nr), basename);
762 char *getSetupFilename(void)
764 static char *filename = NULL;
766 checked_free(filename);
768 filename = getPath2(getSetupDir(), SETUP_FILENAME);
773 char *getDefaultSetupFilename(void)
775 return program.config_filename;
778 char *getPlatformSetupFilename(void)
780 return getPlatformConfigFilename(program.config_filename);
783 char *getEditorSetupFilename(void)
785 static char *filename = NULL;
787 checked_free(filename);
788 filename = getPath2(getCurrentLevelDir(), EDITORSETUP_FILENAME);
790 if (fileExists(filename))
793 checked_free(filename);
794 filename = getPath2(getSetupDir(), EDITORSETUP_FILENAME);
799 char *getHelpAnimFilename(void)
801 static char *filename = NULL;
803 checked_free(filename);
805 filename = getPath2(getCurrentLevelDir(), HELPANIM_FILENAME);
810 char *getHelpTextFilename(void)
812 static char *filename = NULL;
814 checked_free(filename);
816 filename = getPath2(getCurrentLevelDir(), HELPTEXT_FILENAME);
821 char *getLevelSetInfoFilename(void)
823 static char *filename = NULL;
838 for (i = 0; basenames[i] != NULL; i++)
840 checked_free(filename);
841 filename = getPath2(getCurrentLevelDir(), basenames[i]);
843 if (fileExists(filename))
850 static char *getLevelSetTitleMessageBasename(int nr, boolean initial)
852 static char basename[32];
854 sprintf(basename, "%s_%d.txt",
855 (initial ? "titlemessage_initial" : "titlemessage"), nr + 1);
860 char *getLevelSetTitleMessageFilename(int nr, boolean initial)
862 static char *filename = NULL;
864 boolean skip_setup_artwork = FALSE;
866 checked_free(filename);
868 basename = getLevelSetTitleMessageBasename(nr, initial);
870 if (!gfx.override_level_graphics)
872 // 1st try: look for special artwork in current level series directory
873 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
874 if (fileExists(filename))
879 // 2nd try: look for message file in current level set directory
880 filename = getPath2(getCurrentLevelDir(), basename);
881 if (fileExists(filename))
886 // check if there is special artwork configured in level series config
887 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
889 // 3rd try: look for special artwork configured in level series config
890 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
891 if (fileExists(filename))
896 // take missing artwork configured in level set config from default
897 skip_setup_artwork = TRUE;
901 if (!skip_setup_artwork)
903 // 4th try: look for special artwork in configured artwork directory
904 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
905 if (fileExists(filename))
911 // 5th try: look for default artwork in new default artwork directory
912 filename = getPath2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
913 if (fileExists(filename))
918 // 6th try: look for default artwork in old default artwork directory
919 filename = getPath2(options.graphics_directory, basename);
920 if (fileExists(filename))
923 return NULL; // cannot find specified artwork file anywhere
926 static char *getCreditsBasename(int nr)
928 static char basename[32];
930 sprintf(basename, "credits_%d.txt", nr + 1);
935 char *getCreditsFilename(int nr, boolean global)
937 char *basename = getCreditsBasename(nr);
938 char *basepath = NULL;
939 static char *credits_subdir = NULL;
940 static char *filename = NULL;
942 if (credits_subdir == NULL)
943 credits_subdir = getPath2(DOCS_DIRECTORY, CREDITS_DIRECTORY);
945 checked_free(filename);
947 // look for credits file in the game's base or current level set directory
948 basepath = (global ? options.base_directory : getCurrentLevelDir());
950 filename = getPath3(basepath, credits_subdir, basename);
951 if (fileExists(filename))
954 return NULL; // cannot find credits file
957 static char *getProgramInfoBasename(int nr)
959 static char basename[32];
961 sprintf(basename, "program_%d.txt", nr + 1);
966 char *getProgramInfoFilename(int nr)
968 char *basename = getProgramInfoBasename(nr);
969 static char *info_subdir = NULL;
970 static char *filename = NULL;
972 if (info_subdir == NULL)
973 info_subdir = getPath2(DOCS_DIRECTORY, PROGRAM_INFO_DIRECTORY);
975 checked_free(filename);
977 // look for program info file in the game's base directory
978 filename = getPath3(options.base_directory, info_subdir, basename);
979 if (fileExists(filename))
982 return NULL; // cannot find program info file
985 static char *getCorrectedArtworkBasename(char *basename)
990 char *getCustomImageFilename(char *basename)
992 static char *filename = NULL;
993 boolean skip_setup_artwork = FALSE;
995 checked_free(filename);
997 basename = getCorrectedArtworkBasename(basename);
999 if (!gfx.override_level_graphics)
1001 // 1st try: look for special artwork in current level series directory
1002 filename = getImg3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
1003 if (fileExists(filename))
1008 // check if there is special artwork configured in level series config
1009 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
1011 // 2nd try: look for special artwork configured in level series config
1012 filename = getImg2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
1013 if (fileExists(filename))
1018 // take missing artwork configured in level set config from default
1019 skip_setup_artwork = TRUE;
1023 if (!skip_setup_artwork)
1025 // 3rd try: look for special artwork in configured artwork directory
1026 filename = getImg2(getSetupArtworkDir(artwork.gfx_current), basename);
1027 if (fileExists(filename))
1033 // 4th try: look for default artwork in new default artwork directory
1034 filename = getImg2(getDefaultGraphicsDir(GFX_DEFAULT_SUBDIR), basename);
1035 if (fileExists(filename))
1040 // 5th try: look for default artwork in old default artwork directory
1041 filename = getImg2(options.graphics_directory, basename);
1042 if (fileExists(filename))
1045 if (!strEqual(GFX_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1049 WarnUsingFallback(basename);
1051 // 6th try: look for fallback artwork in old default artwork directory
1052 // (needed to prevent errors when trying to access unused artwork files)
1053 filename = getImg2(options.graphics_directory, GFX_FALLBACK_FILENAME);
1054 if (fileExists(filename))
1058 return NULL; // cannot find specified artwork file anywhere
1061 char *getCustomSoundFilename(char *basename)
1063 static char *filename = NULL;
1064 boolean skip_setup_artwork = FALSE;
1066 checked_free(filename);
1068 basename = getCorrectedArtworkBasename(basename);
1070 if (!gfx.override_level_sounds)
1072 // 1st try: look for special artwork in current level series directory
1073 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
1074 if (fileExists(filename))
1079 // check if there is special artwork configured in level series config
1080 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
1082 // 2nd try: look for special artwork configured in level series config
1083 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
1084 if (fileExists(filename))
1089 // take missing artwork configured in level set config from default
1090 skip_setup_artwork = TRUE;
1094 if (!skip_setup_artwork)
1096 // 3rd try: look for special artwork in configured artwork directory
1097 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
1098 if (fileExists(filename))
1104 // 4th try: look for default artwork in new default artwork directory
1105 filename = getPath2(getDefaultSoundsDir(SND_DEFAULT_SUBDIR), basename);
1106 if (fileExists(filename))
1111 // 5th try: look for default artwork in old default artwork directory
1112 filename = getPath2(options.sounds_directory, basename);
1113 if (fileExists(filename))
1116 if (!strEqual(SND_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1120 WarnUsingFallback(basename);
1122 // 6th try: look for fallback artwork in old default artwork directory
1123 // (needed to prevent errors when trying to access unused artwork files)
1124 filename = getPath2(options.sounds_directory, SND_FALLBACK_FILENAME);
1125 if (fileExists(filename))
1129 return NULL; // cannot find specified artwork file anywhere
1132 char *getCustomMusicFilename(char *basename)
1134 static char *filename = NULL;
1135 boolean skip_setup_artwork = FALSE;
1137 checked_free(filename);
1139 basename = getCorrectedArtworkBasename(basename);
1141 if (!gfx.override_level_music)
1143 // 1st try: look for special artwork in current level series directory
1144 filename = getPath3(getCurrentLevelDir(), MUSIC_DIRECTORY, basename);
1145 if (fileExists(filename))
1150 // check if there is special artwork configured in level series config
1151 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1153 // 2nd try: look for special artwork configured in level series config
1154 filename = getPath2(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR), basename);
1155 if (fileExists(filename))
1160 // take missing artwork configured in level set config from default
1161 skip_setup_artwork = TRUE;
1165 if (!skip_setup_artwork)
1167 // 3rd try: look for special artwork in configured artwork directory
1168 filename = getPath2(getSetupArtworkDir(artwork.mus_current), basename);
1169 if (fileExists(filename))
1175 // 4th try: look for default artwork in new default artwork directory
1176 filename = getPath2(getDefaultMusicDir(MUS_DEFAULT_SUBDIR), basename);
1177 if (fileExists(filename))
1182 // 5th try: look for default artwork in old default artwork directory
1183 filename = getPath2(options.music_directory, basename);
1184 if (fileExists(filename))
1187 if (!strEqual(MUS_FALLBACK_FILENAME, UNDEFINED_FILENAME))
1191 WarnUsingFallback(basename);
1193 // 6th try: look for fallback artwork in old default artwork directory
1194 // (needed to prevent errors when trying to access unused artwork files)
1195 filename = getPath2(options.music_directory, MUS_FALLBACK_FILENAME);
1196 if (fileExists(filename))
1200 return NULL; // cannot find specified artwork file anywhere
1203 char *getCustomArtworkFilename(char *basename, int type)
1205 if (type == ARTWORK_TYPE_GRAPHICS)
1206 return getCustomImageFilename(basename);
1207 else if (type == ARTWORK_TYPE_SOUNDS)
1208 return getCustomSoundFilename(basename);
1209 else if (type == ARTWORK_TYPE_MUSIC)
1210 return getCustomMusicFilename(basename);
1212 return UNDEFINED_FILENAME;
1215 char *getCustomArtworkConfigFilename(int type)
1217 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
1220 char *getCustomArtworkLevelConfigFilename(int type)
1222 static char *filename = NULL;
1224 checked_free(filename);
1226 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
1231 char *getCustomMusicDirectory(void)
1233 static char *directory = NULL;
1234 boolean skip_setup_artwork = FALSE;
1236 checked_free(directory);
1238 if (!gfx.override_level_music)
1240 // 1st try: look for special artwork in current level series directory
1241 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
1242 if (directoryExists(directory))
1247 // check if there is special artwork configured in level series config
1248 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
1250 // 2nd try: look for special artwork configured in level series config
1251 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
1252 if (directoryExists(directory))
1257 // take missing artwork configured in level set config from default
1258 skip_setup_artwork = TRUE;
1262 if (!skip_setup_artwork)
1264 // 3rd try: look for special artwork in configured artwork directory
1265 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
1266 if (directoryExists(directory))
1272 // 4th try: look for default artwork in new default artwork directory
1273 directory = getStringCopy(getDefaultMusicDir(MUS_DEFAULT_SUBDIR));
1274 if (directoryExists(directory))
1279 // 5th try: look for default artwork in old default artwork directory
1280 directory = getStringCopy(options.music_directory);
1281 if (directoryExists(directory))
1284 return NULL; // cannot find specified artwork file anywhere
1287 void MarkTapeDirectoryUploadsAsComplete(char *level_subdir)
1289 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1291 touchFile(filename);
1293 checked_free(filename);
1296 void MarkTapeDirectoryUploadsAsIncomplete(char *level_subdir)
1298 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1302 checked_free(filename);
1305 boolean CheckTapeDirectoryUploadsComplete(char *level_subdir)
1307 char *filename = getPath2(getTapeDir(level_subdir), UPLOADED_FILENAME);
1308 boolean success = fileExists(filename);
1310 checked_free(filename);
1315 void InitMissingFileHash(void)
1317 if (missing_file_hash == NULL)
1318 freeSetupFileHash(missing_file_hash);
1320 missing_file_hash = newSetupFileHash();
1323 void InitTapeDirectory(char *level_subdir)
1325 boolean new_tape_dir = !directoryExists(getTapeDir(level_subdir));
1327 createDirectory(getUserGameDataDir(), "user data");
1328 createDirectory(getTapeDir(NULL), "main tape");
1329 createDirectory(getTapeDir(level_subdir), "level tape");
1332 MarkTapeDirectoryUploadsAsComplete(level_subdir);
1335 void InitScoreDirectory(char *level_subdir)
1337 createDirectory(getMainUserGameDataDir(), "main user data");
1338 createDirectory(getScoreDir(NULL), "main score");
1339 createDirectory(getScoreDir(level_subdir), "level score");
1342 void InitScoreCacheDirectory(char *level_subdir)
1344 createDirectory(getMainUserGameDataDir(), "main user data");
1345 createDirectory(getCacheDir(), "cache data");
1346 createDirectory(getScoreCacheDir(NULL), "main score");
1347 createDirectory(getScoreCacheDir(level_subdir), "level score");
1350 void InitScoreTapeDirectory(char *level_subdir, int nr)
1352 InitScoreDirectory(level_subdir);
1354 createDirectory(getScoreTapeDir(level_subdir, nr), "score tape");
1357 void InitScoreCacheTapeDirectory(char *level_subdir, int nr)
1359 InitScoreCacheDirectory(level_subdir);
1361 createDirectory(getScoreCacheTapeDir(level_subdir, nr), "score tape");
1364 static void SaveUserLevelInfo(void);
1366 void InitUserLevelDirectory(char *level_subdir)
1368 if (!directoryExists(getUserLevelDir(level_subdir)))
1370 createDirectory(getMainUserGameDataDir(), "main user data");
1371 createDirectory(getUserLevelDir(NULL), "main user level");
1373 if (setup.internal.create_user_levelset)
1375 createDirectory(getUserLevelDir(level_subdir), "user level");
1377 SaveUserLevelInfo();
1382 void InitNetworkLevelDirectory(char *level_subdir)
1384 if (!directoryExists(getNetworkLevelDir(level_subdir)))
1386 createDirectory(getMainUserGameDataDir(), "main user data");
1387 createDirectory(getNetworkDir(), "network data");
1388 createDirectory(getNetworkLevelDir(NULL), "main network level");
1389 createDirectory(getNetworkLevelDir(level_subdir), "network level");
1393 void InitLevelSetupDirectory(char *level_subdir)
1395 createDirectory(getUserGameDataDir(), "user data");
1396 createDirectory(getLevelSetupDir(NULL), "main level setup");
1397 createDirectory(getLevelSetupDir(level_subdir), "level setup");
1400 static void InitCacheDirectory(void)
1402 createDirectory(getMainUserGameDataDir(), "main user data");
1403 createDirectory(getCacheDir(), "cache data");
1407 // ----------------------------------------------------------------------------
1408 // some functions to handle lists of level and artwork directories
1409 // ----------------------------------------------------------------------------
1411 TreeInfo *newTreeInfo(void)
1413 return checked_calloc(sizeof(TreeInfo));
1416 TreeInfo *newTreeInfo_setDefaults(int type)
1418 TreeInfo *ti = newTreeInfo();
1420 setTreeInfoToDefaults(ti, type);
1425 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
1427 node_new->next = *node_first;
1428 *node_first = node_new;
1431 void removeTreeInfo(TreeInfo **node_first)
1433 TreeInfo *node_old = *node_first;
1435 *node_first = node_old->next;
1436 node_old->next = NULL;
1438 freeTreeInfo(node_old);
1441 int numTreeInfo(TreeInfo *node)
1454 boolean validLevelSeries(TreeInfo *node)
1456 // in a number of cases, tree node is no valid level set
1457 if (node == NULL || node->node_group || node->parent_link || node->is_copy)
1463 TreeInfo *getValidLevelSeries(TreeInfo *node, TreeInfo *default_node)
1465 if (validLevelSeries(node))
1467 else if (node->is_copy)
1468 return getTreeInfoFromIdentifier(leveldir_first, node->identifier);
1470 return getFirstValidTreeInfoEntry(default_node);
1473 static TreeInfo *getValidTreeInfoEntryExt(TreeInfo *node, boolean get_next_node)
1478 if (node->node_group) // enter node group (step down into tree)
1479 return getFirstValidTreeInfoEntry(node->node_group);
1481 if (node->parent_link) // skip first node (back link) of node group
1482 get_next_node = TRUE;
1484 if (!get_next_node) // get current regular tree node
1487 // get next regular tree node, or step up until one is found
1488 while (node->next == NULL && node->node_parent != NULL)
1489 node = node->node_parent;
1491 return getFirstValidTreeInfoEntry(node->next);
1494 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
1496 return getValidTreeInfoEntryExt(node, FALSE);
1499 TreeInfo *getNextValidTreeInfoEntry(TreeInfo *node)
1501 return getValidTreeInfoEntryExt(node, TRUE);
1504 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
1509 if (node->node_parent == NULL) // top level group
1510 return *node->node_top;
1511 else // sub level group
1512 return node->node_parent->node_group;
1515 int numTreeInfoInGroup(TreeInfo *node)
1517 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
1520 int getPosFromTreeInfo(TreeInfo *node)
1522 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
1527 if (node_cmp == node)
1531 node_cmp = node_cmp->next;
1537 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
1539 TreeInfo *node_default = node;
1551 return node_default;
1554 static TreeInfo *getTreeInfoFromIdentifierExt(TreeInfo *node, char *identifier,
1555 int node_type_wanted)
1557 if (identifier == NULL)
1562 if (TREE_NODE_TYPE(node) == node_type_wanted &&
1563 strEqual(identifier, node->identifier))
1566 if (node->node_group)
1568 TreeInfo *node_group = getTreeInfoFromIdentifierExt(node->node_group,
1581 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
1583 return getTreeInfoFromIdentifierExt(node, identifier, TREE_NODE_TYPE_DEFAULT);
1586 static TreeInfo *cloneTreeNode(TreeInfo **node_top, TreeInfo *node_parent,
1587 TreeInfo *node, boolean skip_sets_without_levels)
1594 if (!node->parent_link && !node->level_group &&
1595 skip_sets_without_levels && node->levels == 0)
1596 return cloneTreeNode(node_top, node_parent, node->next,
1597 skip_sets_without_levels);
1599 node_new = getTreeInfoCopy(node); // copy complete node
1601 node_new->node_top = node_top; // correct top node link
1602 node_new->node_parent = node_parent; // correct parent node link
1604 if (node->level_group)
1605 node_new->node_group = cloneTreeNode(node_top, node_new, node->node_group,
1606 skip_sets_without_levels);
1608 node_new->next = cloneTreeNode(node_top, node_parent, node->next,
1609 skip_sets_without_levels);
1614 static void cloneTree(TreeInfo **ti_new, TreeInfo *ti, boolean skip_empty_sets)
1616 TreeInfo *ti_cloned = cloneTreeNode(ti_new, NULL, ti, skip_empty_sets);
1618 *ti_new = ti_cloned;
1621 static boolean adjustTreeGraphicsForEMC(TreeInfo *node)
1623 boolean settings_changed = FALSE;
1627 boolean want_ecs = (setup.prefer_aga_graphics == FALSE);
1628 boolean want_aga = (setup.prefer_aga_graphics == TRUE);
1629 boolean has_only_ecs = (!node->graphics_set && !node->graphics_set_aga);
1630 boolean has_only_aga = (!node->graphics_set && !node->graphics_set_ecs);
1631 char *graphics_set = NULL;
1633 if (node->graphics_set_ecs && (want_ecs || has_only_ecs))
1634 graphics_set = node->graphics_set_ecs;
1636 if (node->graphics_set_aga && (want_aga || has_only_aga))
1637 graphics_set = node->graphics_set_aga;
1639 if (graphics_set && !strEqual(node->graphics_set, graphics_set))
1641 setString(&node->graphics_set, graphics_set);
1642 settings_changed = TRUE;
1645 if (node->node_group != NULL)
1646 settings_changed |= adjustTreeGraphicsForEMC(node->node_group);
1651 return settings_changed;
1654 static boolean adjustTreeSoundsForEMC(TreeInfo *node)
1656 boolean settings_changed = FALSE;
1660 boolean want_default = (setup.prefer_lowpass_sounds == FALSE);
1661 boolean want_lowpass = (setup.prefer_lowpass_sounds == TRUE);
1662 boolean has_only_default = (!node->sounds_set && !node->sounds_set_lowpass);
1663 boolean has_only_lowpass = (!node->sounds_set && !node->sounds_set_default);
1664 char *sounds_set = NULL;
1666 if (node->sounds_set_default && (want_default || has_only_default))
1667 sounds_set = node->sounds_set_default;
1669 if (node->sounds_set_lowpass && (want_lowpass || has_only_lowpass))
1670 sounds_set = node->sounds_set_lowpass;
1672 if (sounds_set && !strEqual(node->sounds_set, sounds_set))
1674 setString(&node->sounds_set, sounds_set);
1675 settings_changed = TRUE;
1678 if (node->node_group != NULL)
1679 settings_changed |= adjustTreeSoundsForEMC(node->node_group);
1684 return settings_changed;
1687 int dumpTreeInfo(TreeInfo *node, int depth)
1689 char bullet_list[] = { '-', '*', 'o' };
1690 int num_leaf_nodes = 0;
1694 Debug("tree", "Dumping TreeInfo:");
1698 char bullet = bullet_list[depth % ARRAY_SIZE(bullet_list)];
1700 for (i = 0; i < depth * 2; i++)
1701 DebugContinued("", " ");
1703 DebugContinued("tree", "%c '%s' ['%s] [PARENT: '%s'] %s\n",
1704 bullet, node->name, node->identifier,
1705 (node->node_parent ? node->node_parent->identifier : "-"),
1706 (node->node_group ? "[GROUP]" :
1707 node->is_copy ? "[COPY]" : ""));
1709 if (!node->node_group && !node->parent_link)
1713 // use for dumping artwork info tree
1714 Debug("tree", "subdir == '%s' ['%s', '%s'] [%d])",
1715 node->subdir, node->fullpath, node->basepath, node->in_user_dir);
1718 if (node->node_group != NULL)
1719 num_leaf_nodes += dumpTreeInfo(node->node_group, depth + 1);
1725 Debug("tree", "Summary: %d leaf nodes found", num_leaf_nodes);
1727 return num_leaf_nodes;
1730 void sortTreeInfoBySortFunction(TreeInfo **node_first,
1731 int (*compare_function)(const void *,
1734 int num_nodes = numTreeInfo(*node_first);
1735 TreeInfo **sort_array;
1736 TreeInfo *node = *node_first;
1742 // allocate array for sorting structure pointers
1743 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
1745 // writing structure pointers to sorting array
1746 while (i < num_nodes && node) // double boundary check...
1748 sort_array[i] = node;
1754 // sorting the structure pointers in the sorting array
1755 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
1758 // update the linkage of list elements with the sorted node array
1759 for (i = 0; i < num_nodes - 1; i++)
1760 sort_array[i]->next = sort_array[i + 1];
1761 sort_array[num_nodes - 1]->next = NULL;
1763 // update the linkage of the main list anchor pointer
1764 *node_first = sort_array[0];
1768 // now recursively sort the level group structures
1772 if (node->node_group != NULL)
1773 sortTreeInfoBySortFunction(&node->node_group, compare_function);
1779 void sortTreeInfo(TreeInfo **node_first)
1781 sortTreeInfoBySortFunction(node_first, compareTreeInfoEntries);
1785 // ============================================================================
1786 // some stuff from "files.c"
1787 // ============================================================================
1789 #if defined(PLATFORM_WINDOWS)
1791 #define S_IRGRP S_IRUSR
1794 #define S_IROTH S_IRUSR
1797 #define S_IWGRP S_IWUSR
1800 #define S_IWOTH S_IWUSR
1803 #define S_IXGRP S_IXUSR
1806 #define S_IXOTH S_IXUSR
1809 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
1814 #endif // PLATFORM_WINDOWS
1816 // file permissions for newly written files
1817 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
1818 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
1819 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
1821 #define MODE_W_PRIVATE (S_IWUSR)
1822 #define MODE_W_PUBLIC_FILE (S_IWUSR | S_IWGRP)
1823 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
1825 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
1826 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
1827 #define DIR_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_X_ALL | MODE_W_ALL)
1829 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
1830 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC_FILE)
1831 #define FILE_PERMS_PUBLIC_ALL (MODE_R_ALL | MODE_W_ALL)
1834 char *getHomeDir(void)
1836 static char *dir = NULL;
1838 #if defined(PLATFORM_WINDOWS)
1841 dir = checked_malloc(MAX_PATH + 1);
1843 if (!SUCCEEDED(SHGetFolderPath(NULL, CSIDL_PERSONAL, NULL, 0, dir)))
1846 #elif defined(PLATFORM_EMSCRIPTEN)
1847 dir = PERSISTENT_DIRECTORY;
1848 #elif defined(PLATFORM_UNIX)
1851 if ((dir = getenv("HOME")) == NULL)
1853 dir = getUnixHomeDir();
1856 dir = getStringCopy(dir);
1868 char *getPersonalDataDir(void)
1870 static char *personal_data_dir = NULL;
1872 #if defined(PLATFORM_MAC)
1873 if (personal_data_dir == NULL)
1874 personal_data_dir = getPath2(getHomeDir(), "Documents");
1876 if (personal_data_dir == NULL)
1877 personal_data_dir = getHomeDir();
1880 return personal_data_dir;
1883 char *getMainUserGameDataDir(void)
1885 static char *main_user_data_dir = NULL;
1887 #if defined(PLATFORM_ANDROID)
1888 if (main_user_data_dir == NULL)
1889 main_user_data_dir = (char *)(SDL_AndroidGetExternalStorageState() &
1890 SDL_ANDROID_EXTERNAL_STORAGE_WRITE ?
1891 SDL_AndroidGetExternalStoragePath() :
1892 SDL_AndroidGetInternalStoragePath());
1894 if (main_user_data_dir == NULL)
1895 main_user_data_dir = getPath2(getPersonalDataDir(),
1896 program.userdata_subdir);
1899 return main_user_data_dir;
1902 char *getUserGameDataDir(void)
1905 return getMainUserGameDataDir();
1907 return getUserDir(user.nr);
1910 char *getSetupDir(void)
1912 return getUserGameDataDir();
1915 static mode_t posix_umask(mode_t mask)
1917 #if defined(PLATFORM_UNIX)
1924 static int posix_mkdir(const char *pathname, mode_t mode)
1926 #if defined(PLATFORM_WINDOWS)
1927 return mkdir(pathname);
1929 return mkdir(pathname, mode);
1933 static boolean posix_process_running_setgid(void)
1935 #if defined(PLATFORM_UNIX)
1936 return (getgid() != getegid());
1942 void createDirectory(char *dir, char *text)
1944 if (directoryExists(dir))
1947 // leave "other" permissions in umask untouched, but ensure group parts
1948 // of USERDATA_DIR_MODE are not masked
1949 int permission_class = PERMS_PRIVATE;
1950 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1951 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1952 mode_t last_umask = posix_umask(0);
1953 mode_t group_umask = ~(dir_mode & S_IRWXG);
1954 int running_setgid = posix_process_running_setgid();
1956 if (permission_class == PERMS_PUBLIC)
1958 // if we're setgid, protect files against "other"
1959 // else keep umask(0) to make the dir world-writable
1962 posix_umask(last_umask & group_umask);
1964 dir_mode = DIR_PERMS_PUBLIC_ALL;
1967 if (posix_mkdir(dir, dir_mode) != 0)
1968 Warn("cannot create %s directory '%s': %s", text, dir, strerror(errno));
1970 if (permission_class == PERMS_PUBLIC && !running_setgid)
1971 chmod(dir, dir_mode);
1973 posix_umask(last_umask); // restore previous umask
1976 void InitMainUserDataDirectory(void)
1978 createDirectory(getMainUserGameDataDir(), "main user data");
1981 void InitUserDataDirectory(void)
1983 createDirectory(getMainUserGameDataDir(), "main user data");
1987 createDirectory(getUserDir(-1), "users");
1988 createDirectory(getUserDir(user.nr), "user data");
1992 void SetFilePermissions(char *filename, int permission_class)
1994 int running_setgid = posix_process_running_setgid();
1995 int perms = (permission_class == PERMS_PRIVATE ?
1996 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC);
1998 if (permission_class == PERMS_PUBLIC && !running_setgid)
1999 perms = FILE_PERMS_PUBLIC_ALL;
2001 chmod(filename, perms);
2004 void fprintFileHeader(FILE *file, char *basename)
2006 char *prefix = "# ";
2009 fprintf_line_with_prefix(file, prefix, sep1, 77);
2010 fprintf(file, "%s%s\n", prefix, basename);
2011 fprintf_line_with_prefix(file, prefix, sep1, 77);
2012 fprintf(file, "\n");
2015 int getFileVersionFromCookieString(const char *cookie)
2017 const char *ptr_cookie1, *ptr_cookie2;
2018 const char *pattern1 = "_FILE_VERSION_";
2019 const char *pattern2 = "?.?";
2020 const int len_cookie = strlen(cookie);
2021 const int len_pattern1 = strlen(pattern1);
2022 const int len_pattern2 = strlen(pattern2);
2023 const int len_pattern = len_pattern1 + len_pattern2;
2024 int version_super, version_major;
2026 if (len_cookie <= len_pattern)
2029 ptr_cookie1 = &cookie[len_cookie - len_pattern];
2030 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
2032 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
2035 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
2036 ptr_cookie2[1] != '.' ||
2037 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
2040 version_super = ptr_cookie2[0] - '0';
2041 version_major = ptr_cookie2[2] - '0';
2043 return VERSION_IDENT(version_super, version_major, 0, 0);
2046 boolean checkCookieString(const char *cookie, const char *template)
2048 const char *pattern = "_FILE_VERSION_?.?";
2049 const int len_cookie = strlen(cookie);
2050 const int len_template = strlen(template);
2051 const int len_pattern = strlen(pattern);
2053 if (len_cookie != len_template)
2056 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
2063 // ----------------------------------------------------------------------------
2064 // setup file list and hash handling functions
2065 // ----------------------------------------------------------------------------
2067 char *getFormattedSetupEntry(char *token, char *value)
2070 static char entry[MAX_LINE_LEN];
2072 // if value is an empty string, just return token without value
2076 // start with the token and some spaces to format output line
2077 sprintf(entry, "%s:", token);
2078 for (i = strlen(entry); i < token_value_position; i++)
2081 // continue with the token's value
2082 strcat(entry, value);
2087 SetupFileList *newSetupFileList(char *token, char *value)
2089 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
2091 new->token = getStringCopy(token);
2092 new->value = getStringCopy(value);
2099 void freeSetupFileList(SetupFileList *list)
2104 checked_free(list->token);
2105 checked_free(list->value);
2108 freeSetupFileList(list->next);
2113 char *getListEntry(SetupFileList *list, char *token)
2118 if (strEqual(list->token, token))
2121 return getListEntry(list->next, token);
2124 SetupFileList *setListEntry(SetupFileList *list, char *token, char *value)
2129 if (strEqual(list->token, token))
2131 checked_free(list->value);
2133 list->value = getStringCopy(value);
2137 else if (list->next == NULL)
2138 return (list->next = newSetupFileList(token, value));
2140 return setListEntry(list->next, token, value);
2143 SetupFileList *addListEntry(SetupFileList *list, char *token, char *value)
2148 if (list->next == NULL)
2149 return (list->next = newSetupFileList(token, value));
2151 return addListEntry(list->next, token, value);
2154 #if ENABLE_UNUSED_CODE
2156 static void printSetupFileList(SetupFileList *list)
2161 Debug("setup:printSetupFileList", "token: '%s'", list->token);
2162 Debug("setup:printSetupFileList", "value: '%s'", list->value);
2164 printSetupFileList(list->next);
2170 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
2171 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
2172 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
2173 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
2175 #define insert_hash_entry hashtable_insert
2176 #define search_hash_entry hashtable_search
2177 #define change_hash_entry hashtable_change
2178 #define remove_hash_entry hashtable_remove
2181 unsigned int get_hash_from_key(void *key)
2186 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
2187 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
2188 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
2189 it works better than many other constants, prime or not) has never been
2190 adequately explained.
2192 If you just want to have a good hash function, and cannot wait, djb2
2193 is one of the best string hash functions i know. It has excellent
2194 distribution and speed on many different sets of keys and table sizes.
2195 You are not likely to do better with one of the "well known" functions
2196 such as PJW, K&R, etc.
2198 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
2201 char *str = (char *)key;
2202 unsigned int hash = 5381;
2205 while ((c = *str++))
2206 hash = ((hash << 5) + hash) + c; // hash * 33 + c
2211 int hash_keys_are_equal(void *key1, void *key2)
2213 return (strEqual((char *)key1, (char *)key2));
2216 SetupFileHash *newSetupFileHash(void)
2218 SetupFileHash *new_hash =
2219 create_hashtable(16, 0.75, get_hash_from_key, hash_keys_are_equal);
2221 if (new_hash == NULL)
2222 Fail("create_hashtable() failed -- out of memory");
2227 void freeSetupFileHash(SetupFileHash *hash)
2232 hashtable_destroy(hash, 1); // 1 == also free values stored in hash
2235 char *getHashEntry(SetupFileHash *hash, char *token)
2240 return search_hash_entry(hash, token);
2243 void setHashEntry(SetupFileHash *hash, char *token, char *value)
2250 value_copy = getStringCopy(value);
2252 // change value; if it does not exist, insert it as new
2253 if (!change_hash_entry(hash, token, value_copy))
2254 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
2255 Fail("cannot insert into hash -- aborting");
2258 char *removeHashEntry(SetupFileHash *hash, char *token)
2263 return remove_hash_entry(hash, token);
2266 #if ENABLE_UNUSED_CODE
2268 static void printSetupFileHash(SetupFileHash *hash)
2270 BEGIN_HASH_ITERATION(hash, itr)
2272 Debug("setup:printSetupFileHash", "token: '%s'", HASH_ITERATION_TOKEN(itr));
2273 Debug("setup:printSetupFileHash", "value: '%s'", HASH_ITERATION_VALUE(itr));
2275 END_HASH_ITERATION(hash, itr)
2280 #define ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE 1
2281 #define CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING 0
2282 #define CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH 0
2284 static boolean token_value_separator_found = FALSE;
2285 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2286 static boolean token_value_separator_warning = FALSE;
2288 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2289 static boolean token_already_exists_warning = FALSE;
2292 static boolean getTokenValueFromSetupLineExt(char *line,
2293 char **token_ptr, char **value_ptr,
2294 char *filename, char *line_raw,
2296 boolean separator_required)
2298 static char line_copy[MAX_LINE_LEN + 1], line_raw_copy[MAX_LINE_LEN + 1];
2299 char *token, *value, *line_ptr;
2301 // when externally invoked via ReadTokenValueFromLine(), copy line buffers
2302 if (line_raw == NULL)
2304 strncpy(line_copy, line, MAX_LINE_LEN);
2305 line_copy[MAX_LINE_LEN] = '\0';
2308 strcpy(line_raw_copy, line_copy);
2309 line_raw = line_raw_copy;
2312 // cut trailing comment from input line
2313 for (line_ptr = line; *line_ptr; line_ptr++)
2315 if (*line_ptr == '#')
2322 // cut trailing whitespaces from input line
2323 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2324 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2327 // ignore empty lines
2331 // cut leading whitespaces from token
2332 for (token = line; *token; token++)
2333 if (*token != ' ' && *token != '\t')
2336 // start with empty value as reliable default
2339 token_value_separator_found = FALSE;
2341 // find end of token to determine start of value
2342 for (line_ptr = token; *line_ptr; line_ptr++)
2344 // first look for an explicit token/value separator, like ':' or '='
2345 if (*line_ptr == ':' || *line_ptr == '=')
2347 *line_ptr = '\0'; // terminate token string
2348 value = line_ptr + 1; // set beginning of value
2350 token_value_separator_found = TRUE;
2356 #if ALLOW_TOKEN_VALUE_SEPARATOR_BEING_WHITESPACE
2357 // fallback: if no token/value separator found, also allow whitespaces
2358 if (!token_value_separator_found && !separator_required)
2360 for (line_ptr = token; *line_ptr; line_ptr++)
2362 if (*line_ptr == ' ' || *line_ptr == '\t')
2364 *line_ptr = '\0'; // terminate token string
2365 value = line_ptr + 1; // set beginning of value
2367 token_value_separator_found = TRUE;
2373 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2374 if (token_value_separator_found)
2376 if (!token_value_separator_warning)
2378 Debug("setup", "---");
2380 if (filename != NULL)
2382 Debug("setup", "missing token/value separator(s) in config file:");
2383 Debug("setup", "- config file: '%s'", filename);
2387 Debug("setup", "missing token/value separator(s):");
2390 token_value_separator_warning = TRUE;
2393 if (filename != NULL)
2394 Debug("setup", "- line %d: '%s'", line_nr, line_raw);
2396 Debug("setup", "- line: '%s'", line_raw);
2402 // cut trailing whitespaces from token
2403 for (line_ptr = &token[strlen(token)]; line_ptr >= token; line_ptr--)
2404 if ((*line_ptr == ' ' || *line_ptr == '\t') && *(line_ptr + 1) == '\0')
2407 // cut leading whitespaces from value
2408 for (; *value; value++)
2409 if (*value != ' ' && *value != '\t')
2418 boolean getTokenValueFromSetupLine(char *line, char **token, char **value)
2420 // while the internal (old) interface does not require a token/value
2421 // separator (for downwards compatibility with existing files which
2422 // don't use them), it is mandatory for the external (new) interface
2424 return getTokenValueFromSetupLineExt(line, token, value, NULL, NULL, 0, TRUE);
2427 static boolean loadSetupFileData(void *setup_file_data, char *filename,
2428 boolean top_recursion_level, boolean is_hash)
2430 static SetupFileHash *include_filename_hash = NULL;
2431 char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN];
2432 char *token, *value, *line_ptr;
2433 void *insert_ptr = NULL;
2434 boolean read_continued_line = FALSE;
2436 int line_nr = 0, token_count = 0, include_count = 0;
2438 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2439 token_value_separator_warning = FALSE;
2442 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2443 token_already_exists_warning = FALSE;
2446 if (!(file = openFile(filename, MODE_READ)))
2448 #if DEBUG_NO_CONFIG_FILE
2449 Debug("setup", "cannot open configuration file '%s'", filename);
2455 // use "insert pointer" to store list end for constant insertion complexity
2457 insert_ptr = setup_file_data;
2459 // on top invocation, create hash to mark included files (to prevent loops)
2460 if (top_recursion_level)
2461 include_filename_hash = newSetupFileHash();
2463 // mark this file as already included (to prevent including it again)
2464 setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true");
2466 while (!checkEndOfFile(file))
2468 // read next line of input file
2469 if (!getStringFromFile(file, line, MAX_LINE_LEN))
2472 // check if line was completely read and is terminated by line break
2473 if (strlen(line) > 0 && line[strlen(line) - 1] == '\n')
2476 // cut trailing line break (this can be newline and/or carriage return)
2477 for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--)
2478 if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0')
2481 // copy raw input line for later use (mainly debugging output)
2482 strcpy(line_raw, line);
2484 if (read_continued_line)
2486 // append new line to existing line, if there is enough space
2487 if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN)
2488 strcat(previous_line, line_ptr);
2490 strcpy(line, previous_line); // copy storage buffer to line
2492 read_continued_line = FALSE;
2495 // if the last character is '\', continue at next line
2496 if (strlen(line) > 0 && line[strlen(line) - 1] == '\\')
2498 line[strlen(line) - 1] = '\0'; // cut off trailing backslash
2499 strcpy(previous_line, line); // copy line to storage buffer
2501 read_continued_line = TRUE;
2506 if (!getTokenValueFromSetupLineExt(line, &token, &value, filename,
2507 line_raw, line_nr, FALSE))
2512 if (strEqual(token, "include"))
2514 if (getHashEntry(include_filename_hash, value) == NULL)
2516 char *basepath = getBasePath(filename);
2517 char *basename = getBaseName(value);
2518 char *filename_include = getPath2(basepath, basename);
2520 loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash);
2524 free(filename_include);
2530 Warn("ignoring already processed file '%s'", value);
2537 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2539 getHashEntry((SetupFileHash *)setup_file_data, token);
2541 if (old_value != NULL)
2543 if (!token_already_exists_warning)
2545 Debug("setup", "---");
2546 Debug("setup", "duplicate token(s) found in config file:");
2547 Debug("setup", "- config file: '%s'", filename);
2549 token_already_exists_warning = TRUE;
2552 Debug("setup", "- token: '%s' (in line %d)", token, line_nr);
2553 Debug("setup", " old value: '%s'", old_value);
2554 Debug("setup", " new value: '%s'", value);
2558 setHashEntry((SetupFileHash *)setup_file_data, token, value);
2562 insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value);
2572 #if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING
2573 if (token_value_separator_warning)
2574 Debug("setup", "---");
2577 #if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH
2578 if (token_already_exists_warning)
2579 Debug("setup", "---");
2582 if (token_count == 0 && include_count == 0)
2583 Warn("configuration file '%s' is empty", filename);
2585 if (top_recursion_level)
2586 freeSetupFileHash(include_filename_hash);
2591 static int compareSetupFileData(const void *object1, const void *object2)
2593 const struct ConfigInfo *entry1 = (struct ConfigInfo *)object1;
2594 const struct ConfigInfo *entry2 = (struct ConfigInfo *)object2;
2596 return strcmp(entry1->token, entry2->token);
2599 static void saveSetupFileHash(SetupFileHash *hash, char *filename)
2601 int item_count = hashtable_count(hash);
2602 int item_size = sizeof(struct ConfigInfo);
2603 struct ConfigInfo *sort_array = checked_malloc(item_count * item_size);
2607 // copy string pointers from hash to array
2608 BEGIN_HASH_ITERATION(hash, itr)
2610 sort_array[i].token = HASH_ITERATION_TOKEN(itr);
2611 sort_array[i].value = HASH_ITERATION_VALUE(itr);
2615 if (i > item_count) // should never happen
2618 END_HASH_ITERATION(hash, itr)
2620 // sort string pointers from hash in array
2621 qsort(sort_array, item_count, item_size, compareSetupFileData);
2623 if (!(file = fopen(filename, MODE_WRITE)))
2625 Warn("cannot write configuration file '%s'", filename);
2630 fprintf(file, "%s\n\n", getFormattedSetupEntry("program.version",
2631 program.version_string));
2632 for (i = 0; i < item_count; i++)
2633 fprintf(file, "%s\n", getFormattedSetupEntry(sort_array[i].token,
2634 sort_array[i].value));
2637 checked_free(sort_array);
2640 SetupFileList *loadSetupFileList(char *filename)
2642 SetupFileList *setup_file_list = newSetupFileList("", "");
2643 SetupFileList *first_valid_list_entry;
2645 if (!loadSetupFileData(setup_file_list, filename, TRUE, FALSE))
2647 freeSetupFileList(setup_file_list);
2652 first_valid_list_entry = setup_file_list->next;
2654 // free empty list header
2655 setup_file_list->next = NULL;
2656 freeSetupFileList(setup_file_list);
2658 return first_valid_list_entry;
2661 SetupFileHash *loadSetupFileHash(char *filename)
2663 SetupFileHash *setup_file_hash = newSetupFileHash();
2665 if (!loadSetupFileData(setup_file_hash, filename, TRUE, TRUE))
2667 freeSetupFileHash(setup_file_hash);
2672 return setup_file_hash;
2676 // ============================================================================
2678 // ============================================================================
2680 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
2681 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
2682 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
2683 #define TOKEN_STR_LAST_USER "last_user"
2685 // level directory info
2686 #define LEVELINFO_TOKEN_IDENTIFIER 0
2687 #define LEVELINFO_TOKEN_NAME 1
2688 #define LEVELINFO_TOKEN_NAME_SORTING 2
2689 #define LEVELINFO_TOKEN_AUTHOR 3
2690 #define LEVELINFO_TOKEN_YEAR 4
2691 #define LEVELINFO_TOKEN_PROGRAM_TITLE 5
2692 #define LEVELINFO_TOKEN_PROGRAM_COPYRIGHT 6
2693 #define LEVELINFO_TOKEN_PROGRAM_COMPANY 7
2694 #define LEVELINFO_TOKEN_IMPORTED_FROM 8
2695 #define LEVELINFO_TOKEN_IMPORTED_BY 9
2696 #define LEVELINFO_TOKEN_TESTED_BY 10
2697 #define LEVELINFO_TOKEN_LEVELS 11
2698 #define LEVELINFO_TOKEN_FIRST_LEVEL 12
2699 #define LEVELINFO_TOKEN_SORT_PRIORITY 13
2700 #define LEVELINFO_TOKEN_LATEST_ENGINE 14
2701 #define LEVELINFO_TOKEN_LEVEL_GROUP 15
2702 #define LEVELINFO_TOKEN_READONLY 16
2703 #define LEVELINFO_TOKEN_GRAPHICS_SET_ECS 17
2704 #define LEVELINFO_TOKEN_GRAPHICS_SET_AGA 18
2705 #define LEVELINFO_TOKEN_GRAPHICS_SET 19
2706 #define LEVELINFO_TOKEN_SOUNDS_SET_DEFAULT 20
2707 #define LEVELINFO_TOKEN_SOUNDS_SET_LOWPASS 21
2708 #define LEVELINFO_TOKEN_SOUNDS_SET 22
2709 #define LEVELINFO_TOKEN_MUSIC_SET 23
2710 #define LEVELINFO_TOKEN_FILENAME 24
2711 #define LEVELINFO_TOKEN_FILETYPE 25
2712 #define LEVELINFO_TOKEN_SPECIAL_FLAGS 26
2713 #define LEVELINFO_TOKEN_EMPTY_LEVEL_NAME 27
2714 #define LEVELINFO_TOKEN_FORCE_LEVEL_NAME 28
2715 #define LEVELINFO_TOKEN_HANDICAP 29
2716 #define LEVELINFO_TOKEN_TIME_LIMIT 30
2717 #define LEVELINFO_TOKEN_SKIP_LEVELS 31
2718 #define LEVELINFO_TOKEN_USE_EMC_TILES 32
2720 #define NUM_LEVELINFO_TOKENS 33
2722 static LevelDirTree ldi;
2724 static struct TokenInfo levelinfo_tokens[] =
2726 // level directory info
2727 { TYPE_STRING, &ldi.identifier, "identifier" },
2728 { TYPE_STRING, &ldi.name, "name" },
2729 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2730 { TYPE_STRING, &ldi.author, "author" },
2731 { TYPE_STRING, &ldi.year, "year" },
2732 { TYPE_STRING, &ldi.program_title, "program_title" },
2733 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2734 { TYPE_STRING, &ldi.program_company, "program_company" },
2735 { TYPE_STRING, &ldi.imported_from, "imported_from" },
2736 { TYPE_STRING, &ldi.imported_by, "imported_by" },
2737 { TYPE_STRING, &ldi.tested_by, "tested_by" },
2738 { TYPE_INTEGER, &ldi.levels, "levels" },
2739 { TYPE_INTEGER, &ldi.first_level, "first_level" },
2740 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2741 { TYPE_BOOLEAN, &ldi.latest_engine, "latest_engine" },
2742 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
2743 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
2744 { TYPE_STRING, &ldi.graphics_set_ecs, "graphics_set.ecs" },
2745 { TYPE_STRING, &ldi.graphics_set_aga, "graphics_set.aga" },
2746 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
2747 { TYPE_STRING, &ldi.sounds_set_default,"sounds_set.default" },
2748 { TYPE_STRING, &ldi.sounds_set_lowpass,"sounds_set.lowpass" },
2749 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
2750 { TYPE_STRING, &ldi.music_set, "music_set" },
2751 { TYPE_STRING, &ldi.level_filename, "filename" },
2752 { TYPE_STRING, &ldi.level_filetype, "filetype" },
2753 { TYPE_STRING, &ldi.special_flags, "special_flags" },
2754 { TYPE_STRING, &ldi.empty_level_name, "empty_level_name" },
2755 { TYPE_BOOLEAN, &ldi.force_level_name, "force_level_name" },
2756 { TYPE_BOOLEAN, &ldi.handicap, "handicap" },
2757 { TYPE_BOOLEAN, &ldi.time_limit, "time_limit" },
2758 { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" },
2759 { TYPE_BOOLEAN, &ldi.use_emc_tiles, "use_emc_tiles" }
2762 static struct TokenInfo artworkinfo_tokens[] =
2764 // artwork directory info
2765 { TYPE_STRING, &ldi.identifier, "identifier" },
2766 { TYPE_STRING, &ldi.subdir, "subdir" },
2767 { TYPE_STRING, &ldi.name, "name" },
2768 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
2769 { TYPE_STRING, &ldi.author, "author" },
2770 { TYPE_STRING, &ldi.program_title, "program_title" },
2771 { TYPE_STRING, &ldi.program_copyright, "program_copyright" },
2772 { TYPE_STRING, &ldi.program_company, "program_company" },
2773 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
2774 { TYPE_STRING, &ldi.basepath, "basepath" },
2775 { TYPE_STRING, &ldi.fullpath, "fullpath" },
2776 { TYPE_BOOLEAN, &ldi.in_user_dir, "in_user_dir" },
2777 { TYPE_STRING, &ldi.class_desc, "class_desc" },
2782 static char *optional_tokens[] =
2785 "program_copyright",
2791 static void setTreeInfoToDefaults(TreeInfo *ti, int type)
2795 ti->node_top = (ti->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
2796 ti->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
2797 ti->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
2798 ti->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
2801 ti->node_parent = NULL;
2802 ti->node_group = NULL;
2809 ti->fullpath = NULL;
2810 ti->basepath = NULL;
2811 ti->identifier = NULL;
2812 ti->name = getStringCopy(ANONYMOUS_NAME);
2813 ti->name_sorting = NULL;
2814 ti->author = getStringCopy(ANONYMOUS_NAME);
2817 ti->program_title = NULL;
2818 ti->program_copyright = NULL;
2819 ti->program_company = NULL;
2821 ti->sort_priority = LEVELCLASS_UNDEFINED; // default: least priority
2822 ti->latest_engine = FALSE; // default: get from level
2823 ti->parent_link = FALSE;
2824 ti->is_copy = FALSE;
2825 ti->in_user_dir = FALSE;
2826 ti->user_defined = FALSE;
2828 ti->class_desc = NULL;
2830 ti->infotext = getStringCopy(TREE_INFOTEXT(ti->type));
2832 if (ti->type == TREE_TYPE_LEVEL_DIR)
2834 ti->imported_from = NULL;
2835 ti->imported_by = NULL;
2836 ti->tested_by = NULL;
2838 ti->graphics_set_ecs = NULL;
2839 ti->graphics_set_aga = NULL;
2840 ti->graphics_set = NULL;
2841 ti->sounds_set_default = NULL;
2842 ti->sounds_set_lowpass = NULL;
2843 ti->sounds_set = NULL;
2844 ti->music_set = NULL;
2845 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2846 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2847 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2849 ti->level_filename = NULL;
2850 ti->level_filetype = NULL;
2852 ti->special_flags = NULL;
2854 ti->empty_level_name = NULL;
2855 ti->force_level_name = FALSE;
2858 ti->first_level = 0;
2860 ti->level_group = FALSE;
2861 ti->handicap_level = 0;
2862 ti->readonly = TRUE;
2863 ti->handicap = TRUE;
2864 ti->time_limit = TRUE;
2865 ti->skip_levels = FALSE;
2867 ti->use_emc_tiles = FALSE;
2871 static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent)
2875 Warn("setTreeInfoToDefaultsFromParent(): parent == NULL");
2877 setTreeInfoToDefaults(ti, TREE_TYPE_UNDEFINED);
2882 // copy all values from the parent structure
2884 ti->type = parent->type;
2886 ti->node_top = parent->node_top;
2887 ti->node_parent = parent;
2888 ti->node_group = NULL;
2895 ti->fullpath = NULL;
2896 ti->basepath = NULL;
2897 ti->identifier = NULL;
2898 ti->name = getStringCopy(ANONYMOUS_NAME);
2899 ti->name_sorting = NULL;
2900 ti->author = getStringCopy(parent->author);
2901 ti->year = getStringCopy(parent->year);
2903 ti->program_title = getStringCopy(parent->program_title);
2904 ti->program_copyright = getStringCopy(parent->program_copyright);
2905 ti->program_company = getStringCopy(parent->program_company);
2907 ti->sort_priority = parent->sort_priority;
2908 ti->latest_engine = parent->latest_engine;
2909 ti->parent_link = FALSE;
2910 ti->is_copy = FALSE;
2911 ti->in_user_dir = parent->in_user_dir;
2912 ti->user_defined = parent->user_defined;
2913 ti->color = parent->color;
2914 ti->class_desc = getStringCopy(parent->class_desc);
2916 ti->infotext = getStringCopy(parent->infotext);
2918 if (ti->type == TREE_TYPE_LEVEL_DIR)
2920 ti->imported_from = getStringCopy(parent->imported_from);
2921 ti->imported_by = getStringCopy(parent->imported_by);
2922 ti->tested_by = getStringCopy(parent->tested_by);
2924 ti->graphics_set_ecs = getStringCopy(parent->graphics_set_ecs);
2925 ti->graphics_set_aga = getStringCopy(parent->graphics_set_aga);
2926 ti->graphics_set = getStringCopy(parent->graphics_set);
2927 ti->sounds_set_default = getStringCopy(parent->sounds_set_default);
2928 ti->sounds_set_lowpass = getStringCopy(parent->sounds_set_lowpass);
2929 ti->sounds_set = getStringCopy(parent->sounds_set);
2930 ti->music_set = getStringCopy(parent->music_set);
2931 ti->graphics_path = getStringCopy(UNDEFINED_FILENAME);
2932 ti->sounds_path = getStringCopy(UNDEFINED_FILENAME);
2933 ti->music_path = getStringCopy(UNDEFINED_FILENAME);
2935 ti->level_filename = getStringCopy(parent->level_filename);
2936 ti->level_filetype = getStringCopy(parent->level_filetype);
2938 ti->special_flags = getStringCopy(parent->special_flags);
2940 ti->empty_level_name = getStringCopy(parent->empty_level_name);
2941 ti->force_level_name = parent->force_level_name;
2943 ti->levels = parent->levels;
2944 ti->first_level = parent->first_level;
2945 ti->last_level = parent->last_level;
2946 ti->level_group = FALSE;
2947 ti->handicap_level = parent->handicap_level;
2948 ti->readonly = parent->readonly;
2949 ti->handicap = parent->handicap;
2950 ti->time_limit = parent->time_limit;
2951 ti->skip_levels = parent->skip_levels;
2953 ti->use_emc_tiles = parent->use_emc_tiles;
2957 static TreeInfo *getTreeInfoCopy(TreeInfo *ti)
2959 TreeInfo *ti_copy = newTreeInfo();
2961 // copy all values from the original structure
2963 ti_copy->type = ti->type;
2965 ti_copy->node_top = ti->node_top;
2966 ti_copy->node_parent = ti->node_parent;
2967 ti_copy->node_group = ti->node_group;
2968 ti_copy->next = ti->next;
2970 ti_copy->cl_first = ti->cl_first;
2971 ti_copy->cl_cursor = ti->cl_cursor;
2973 ti_copy->subdir = getStringCopy(ti->subdir);
2974 ti_copy->fullpath = getStringCopy(ti->fullpath);
2975 ti_copy->basepath = getStringCopy(ti->basepath);
2976 ti_copy->identifier = getStringCopy(ti->identifier);
2977 ti_copy->name = getStringCopy(ti->name);
2978 ti_copy->name_sorting = getStringCopy(ti->name_sorting);
2979 ti_copy->author = getStringCopy(ti->author);
2980 ti_copy->year = getStringCopy(ti->year);
2982 ti_copy->program_title = getStringCopy(ti->program_title);
2983 ti_copy->program_copyright = getStringCopy(ti->program_copyright);
2984 ti_copy->program_company = getStringCopy(ti->program_company);
2986 ti_copy->imported_from = getStringCopy(ti->imported_from);
2987 ti_copy->imported_by = getStringCopy(ti->imported_by);
2988 ti_copy->tested_by = getStringCopy(ti->tested_by);
2990 ti_copy->graphics_set_ecs = getStringCopy(ti->graphics_set_ecs);
2991 ti_copy->graphics_set_aga = getStringCopy(ti->graphics_set_aga);
2992 ti_copy->graphics_set = getStringCopy(ti->graphics_set);
2993 ti_copy->sounds_set_default = getStringCopy(ti->sounds_set_default);
2994 ti_copy->sounds_set_lowpass = getStringCopy(ti->sounds_set_lowpass);
2995 ti_copy->sounds_set = getStringCopy(ti->sounds_set);
2996 ti_copy->music_set = getStringCopy(ti->music_set);
2997 ti_copy->graphics_path = getStringCopy(ti->graphics_path);
2998 ti_copy->sounds_path = getStringCopy(ti->sounds_path);
2999 ti_copy->music_path = getStringCopy(ti->music_path);
3001 ti_copy->level_filename = getStringCopy(ti->level_filename);
3002 ti_copy->level_filetype = getStringCopy(ti->level_filetype);
3004 ti_copy->special_flags = getStringCopy(ti->special_flags);
3006 ti_copy->empty_level_name = getStringCopy(ti->empty_level_name);
3007 ti_copy->force_level_name = ti->force_level_name;
3009 ti_copy->levels = ti->levels;
3010 ti_copy->first_level = ti->first_level;
3011 ti_copy->last_level = ti->last_level;
3012 ti_copy->sort_priority = ti->sort_priority;
3014 ti_copy->latest_engine = ti->latest_engine;
3016 ti_copy->level_group = ti->level_group;
3017 ti_copy->parent_link = ti->parent_link;
3018 ti_copy->is_copy = ti->is_copy;
3019 ti_copy->in_user_dir = ti->in_user_dir;
3020 ti_copy->user_defined = ti->user_defined;
3021 ti_copy->readonly = ti->readonly;
3022 ti_copy->handicap = ti->handicap;
3023 ti_copy->time_limit = ti->time_limit;
3024 ti_copy->skip_levels = ti->skip_levels;
3026 ti_copy->use_emc_tiles = ti->use_emc_tiles;
3028 ti_copy->color = ti->color;
3029 ti_copy->class_desc = getStringCopy(ti->class_desc);
3030 ti_copy->handicap_level = ti->handicap_level;
3032 ti_copy->infotext = getStringCopy(ti->infotext);
3037 void freeTreeInfo(TreeInfo *ti)
3042 checked_free(ti->subdir);
3043 checked_free(ti->fullpath);
3044 checked_free(ti->basepath);
3045 checked_free(ti->identifier);
3047 checked_free(ti->name);
3048 checked_free(ti->name_sorting);
3049 checked_free(ti->author);
3050 checked_free(ti->year);
3052 checked_free(ti->program_title);
3053 checked_free(ti->program_copyright);
3054 checked_free(ti->program_company);
3056 checked_free(ti->class_desc);
3058 checked_free(ti->infotext);
3060 if (ti->type == TREE_TYPE_LEVEL_DIR)
3062 checked_free(ti->imported_from);
3063 checked_free(ti->imported_by);
3064 checked_free(ti->tested_by);
3066 checked_free(ti->graphics_set_ecs);
3067 checked_free(ti->graphics_set_aga);
3068 checked_free(ti->graphics_set);
3069 checked_free(ti->sounds_set_default);
3070 checked_free(ti->sounds_set_lowpass);
3071 checked_free(ti->sounds_set);
3072 checked_free(ti->music_set);
3074 checked_free(ti->graphics_path);
3075 checked_free(ti->sounds_path);
3076 checked_free(ti->music_path);
3078 checked_free(ti->level_filename);
3079 checked_free(ti->level_filetype);
3081 checked_free(ti->special_flags);
3084 // recursively free child node
3086 freeTreeInfo(ti->node_group);
3088 // recursively free next node
3090 freeTreeInfo(ti->next);
3095 void setSetupInfo(struct TokenInfo *token_info,
3096 int token_nr, char *token_value)
3098 int token_type = token_info[token_nr].type;
3099 void *setup_value = token_info[token_nr].value;
3101 if (token_value == NULL)
3104 // set setup field to corresponding token value
3109 *(boolean *)setup_value = get_boolean_from_string(token_value);
3113 *(int *)setup_value = get_switch3_from_string(token_value);
3117 *(Key *)setup_value = getKeyFromKeyName(token_value);
3121 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
3125 *(int *)setup_value = get_integer_from_string(token_value);
3129 checked_free(*(char **)setup_value);
3130 *(char **)setup_value = getStringCopy(token_value);
3134 *(int *)setup_value = get_player_nr_from_string(token_value);
3142 static int compareTreeInfoEntries(const void *object1, const void *object2)
3144 const TreeInfo *entry1 = *((TreeInfo **)object1);
3145 const TreeInfo *entry2 = *((TreeInfo **)object2);
3146 int tree_sorting1 = TREE_SORTING(entry1);
3147 int tree_sorting2 = TREE_SORTING(entry2);
3149 if (tree_sorting1 != tree_sorting2)
3150 return (tree_sorting1 - tree_sorting2);
3152 return strcasecmp(entry1->name_sorting, entry2->name_sorting);
3155 static TreeInfo *createParentTreeInfoNode(TreeInfo *node_parent)
3159 if (node_parent == NULL)
3162 ti_new = newTreeInfo();
3163 setTreeInfoToDefaults(ti_new, node_parent->type);
3165 ti_new->node_parent = node_parent;
3166 ti_new->parent_link = TRUE;
3168 setString(&ti_new->identifier, node_parent->identifier);
3169 setString(&ti_new->name, BACKLINK_TEXT_PARENT);
3170 setString(&ti_new->name_sorting, ti_new->name);
3172 setString(&ti_new->subdir, STRING_PARENT_DIRECTORY);
3173 setString(&ti_new->fullpath, node_parent->fullpath);
3175 ti_new->sort_priority = LEVELCLASS_PARENT;
3176 ti_new->latest_engine = node_parent->latest_engine;
3178 setString(&ti_new->class_desc, getLevelClassDescription(ti_new));
3180 pushTreeInfo(&node_parent->node_group, ti_new);
3185 static TreeInfo *createTopTreeInfoNode(TreeInfo *node_first)
3187 if (node_first == NULL)
3190 TreeInfo *ti_new = newTreeInfo();
3191 int type = node_first->type;
3193 setTreeInfoToDefaults(ti_new, type);
3195 ti_new->node_parent = NULL;
3196 ti_new->parent_link = FALSE;
3198 setString(&ti_new->identifier, "top_tree_node");
3199 setString(&ti_new->name, TREE_INFOTEXT(type));
3200 setString(&ti_new->name_sorting, ti_new->name);
3202 setString(&ti_new->subdir, STRING_TOP_DIRECTORY);
3203 setString(&ti_new->fullpath, ".");
3205 ti_new->sort_priority = LEVELCLASS_TOP;
3206 ti_new->latest_engine = node_first->latest_engine;
3208 setString(&ti_new->class_desc, TREE_INFOTEXT(type));
3210 ti_new->node_group = node_first;
3211 ti_new->level_group = TRUE;
3213 TreeInfo *ti_new2 = createParentTreeInfoNode(ti_new);
3215 setString(&ti_new2->name, TREE_BACKLINK_TEXT(type));
3216 setString(&ti_new2->name_sorting, ti_new2->name);
3221 static void setTreeInfoParentNodes(TreeInfo *node, TreeInfo *node_parent)
3225 if (node->node_group)
3226 setTreeInfoParentNodes(node->node_group, node);
3228 node->node_parent = node_parent;
3234 TreeInfo *addTopTreeInfoNode(TreeInfo *node_first)
3236 // add top tree node with back link node in previous tree
3237 node_first = createTopTreeInfoNode(node_first);
3239 // set all parent links (back links) in complete tree
3240 setTreeInfoParentNodes(node_first, NULL);
3246 // ----------------------------------------------------------------------------
3247 // functions for handling level and custom artwork info cache
3248 // ----------------------------------------------------------------------------
3250 static void LoadArtworkInfoCache(void)
3252 InitCacheDirectory();
3254 if (artworkinfo_cache_old == NULL)
3256 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3258 // try to load artwork info hash from already existing cache file
3259 artworkinfo_cache_old = loadSetupFileHash(filename);
3261 // try to get program version that artwork info cache was written with
3262 char *version = getHashEntry(artworkinfo_cache_old, "program.version");
3264 // check program version of artwork info cache against current version
3265 if (!strEqual(version, program.version_string))
3267 freeSetupFileHash(artworkinfo_cache_old);
3269 artworkinfo_cache_old = NULL;
3272 // if no artwork info cache file was found, start with empty hash
3273 if (artworkinfo_cache_old == NULL)
3274 artworkinfo_cache_old = newSetupFileHash();
3279 if (artworkinfo_cache_new == NULL)
3280 artworkinfo_cache_new = newSetupFileHash();
3282 update_artworkinfo_cache = FALSE;
3285 static void SaveArtworkInfoCache(void)
3287 if (!update_artworkinfo_cache)
3290 char *filename = getPath2(getCacheDir(), ARTWORKINFO_CACHE_FILE);
3292 InitCacheDirectory();
3294 saveSetupFileHash(artworkinfo_cache_new, filename);
3299 static char *getCacheTokenPrefix(char *prefix1, char *prefix2)
3301 static char *prefix = NULL;
3303 checked_free(prefix);
3305 prefix = getStringCat2WithSeparator(prefix1, prefix2, ".");
3310 // (identical to above function, but separate string buffer needed -- nasty)
3311 static char *getCacheToken(char *prefix, char *suffix)
3313 static char *token = NULL;
3315 checked_free(token);
3317 token = getStringCat2WithSeparator(prefix, suffix, ".");
3322 static char *getFileTimestampString(char *filename)
3324 return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename)));
3327 static boolean modifiedFileTimestamp(char *filename, char *timestamp_string)
3329 struct stat file_status;
3331 if (timestamp_string == NULL)
3334 if (!fileExists(filename)) // file does not exist
3335 return (atoi(timestamp_string) != 0);
3337 if (stat(filename, &file_status) != 0) // cannot stat file
3340 return (file_status.st_mtime != atoi(timestamp_string));
3343 static TreeInfo *getArtworkInfoCacheEntry(LevelDirTree *level_node, int type)
3345 char *identifier = level_node->subdir;
3346 char *type_string = ARTWORK_DIRECTORY(type);
3347 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3348 char *token_main = getCacheToken(token_prefix, "CACHED");
3349 char *cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3350 boolean cached = (cache_entry != NULL && strEqual(cache_entry, "true"));
3351 TreeInfo *artwork_info = NULL;
3353 if (!use_artworkinfo_cache)
3356 if (optional_tokens_hash == NULL)
3360 // create hash from list of optional tokens (for quick access)
3361 optional_tokens_hash = newSetupFileHash();
3362 for (i = 0; optional_tokens[i] != NULL; i++)
3363 setHashEntry(optional_tokens_hash, optional_tokens[i], "");
3370 artwork_info = newTreeInfo();
3371 setTreeInfoToDefaults(artwork_info, type);
3373 // set all structure fields according to the token/value pairs
3374 ldi = *artwork_info;
3375 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3377 char *token_suffix = artworkinfo_tokens[i].text;
3378 char *token = getCacheToken(token_prefix, token_suffix);
3379 char *value = getHashEntry(artworkinfo_cache_old, token);
3381 (getHashEntry(optional_tokens_hash, token_suffix) != NULL);
3383 setSetupInfo(artworkinfo_tokens, i, value);
3385 // check if cache entry for this item is mandatory, but missing
3386 if (value == NULL && !optional)
3388 Warn("missing cache entry '%s'", token);
3394 *artwork_info = ldi;
3399 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3400 LEVELINFO_FILENAME);
3401 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3402 ARTWORKINFO_FILENAME(type));
3404 // check if corresponding "levelinfo.conf" file has changed
3405 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3406 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3408 if (modifiedFileTimestamp(filename_levelinfo, cache_entry))
3411 // check if corresponding "<artworkinfo>.conf" file has changed
3412 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3413 cache_entry = getHashEntry(artworkinfo_cache_old, token_main);
3415 if (modifiedFileTimestamp(filename_artworkinfo, cache_entry))
3418 checked_free(filename_levelinfo);
3419 checked_free(filename_artworkinfo);
3422 if (!cached && artwork_info != NULL)
3424 freeTreeInfo(artwork_info);
3429 return artwork_info;
3432 static void setArtworkInfoCacheEntry(TreeInfo *artwork_info,
3433 LevelDirTree *level_node, int type)
3435 char *identifier = level_node->subdir;
3436 char *type_string = ARTWORK_DIRECTORY(type);
3437 char *token_prefix = getCacheTokenPrefix(type_string, identifier);
3438 char *token_main = getCacheToken(token_prefix, "CACHED");
3439 boolean set_cache_timestamps = TRUE;
3442 setHashEntry(artworkinfo_cache_new, token_main, "true");
3444 if (set_cache_timestamps)
3446 char *filename_levelinfo = getPath2(getLevelDirFromTreeInfo(level_node),
3447 LEVELINFO_FILENAME);
3448 char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info),
3449 ARTWORKINFO_FILENAME(type));
3450 char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo);
3451 char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo);
3453 token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO");
3454 setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo);
3456 token_main = getCacheToken(token_prefix, "TIMESTAMP_ARTWORKINFO");
3457 setHashEntry(artworkinfo_cache_new, token_main, timestamp_artworkinfo);
3459 checked_free(filename_levelinfo);
3460 checked_free(filename_artworkinfo);
3461 checked_free(timestamp_levelinfo);
3462 checked_free(timestamp_artworkinfo);
3465 ldi = *artwork_info;
3466 for (i = 0; artworkinfo_tokens[i].type != -1; i++)
3468 char *token = getCacheToken(token_prefix, artworkinfo_tokens[i].text);
3469 char *value = getSetupValue(artworkinfo_tokens[i].type,
3470 artworkinfo_tokens[i].value);
3472 setHashEntry(artworkinfo_cache_new, token, value);
3477 // ----------------------------------------------------------------------------
3478 // functions for loading level info and custom artwork info
3479 // ----------------------------------------------------------------------------
3481 int GetZipFileTreeType(char *zip_filename)
3483 static char *top_dir_path = NULL;
3484 static char *top_dir_conf_filename[NUM_BASE_TREE_TYPES] = { NULL };
3485 static char *conf_basename[NUM_BASE_TREE_TYPES] =
3487 GRAPHICSINFO_FILENAME,
3488 SOUNDSINFO_FILENAME,
3494 checked_free(top_dir_path);
3495 top_dir_path = NULL;
3497 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3499 checked_free(top_dir_conf_filename[j]);
3500 top_dir_conf_filename[j] = NULL;
3503 char **zip_entries = zip_list(zip_filename);
3505 // check if zip file successfully opened
3506 if (zip_entries == NULL || zip_entries[0] == NULL)
3507 return TREE_TYPE_UNDEFINED;
3509 // first zip file entry is expected to be top level directory
3510 char *top_dir = zip_entries[0];
3512 // check if valid top level directory found in zip file
3513 if (!strSuffix(top_dir, "/"))
3514 return TREE_TYPE_UNDEFINED;
3516 // get filenames of valid configuration files in top level directory
3517 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3518 top_dir_conf_filename[j] = getStringCat2(top_dir, conf_basename[j]);
3520 int tree_type = TREE_TYPE_UNDEFINED;
3523 while (zip_entries[e] != NULL)
3525 // check if every zip file entry is below top level directory
3526 if (!strPrefix(zip_entries[e], top_dir))
3527 return TREE_TYPE_UNDEFINED;
3529 // check if this zip file entry is a valid configuration filename
3530 for (j = 0; j < NUM_BASE_TREE_TYPES; j++)
3532 if (strEqual(zip_entries[e], top_dir_conf_filename[j]))
3534 // only exactly one valid configuration file allowed
3535 if (tree_type != TREE_TYPE_UNDEFINED)
3536 return TREE_TYPE_UNDEFINED;
3548 static boolean CheckZipFileForDirectory(char *zip_filename, char *directory,
3551 static char *top_dir_path = NULL;
3552 static char *top_dir_conf_filename = NULL;
3554 checked_free(top_dir_path);
3555 checked_free(top_dir_conf_filename);
3557 top_dir_path = NULL;
3558 top_dir_conf_filename = NULL;
3560 char *conf_basename = (tree_type == TREE_TYPE_LEVEL_DIR ? LEVELINFO_FILENAME :
3561 ARTWORKINFO_FILENAME(tree_type));
3563 // check if valid configuration filename determined
3564 if (conf_basename == NULL || strEqual(conf_basename, ""))
3567 char **zip_entries = zip_list(zip_filename);
3569 // check if zip file successfully opened
3570 if (zip_entries == NULL || zip_entries[0] == NULL)
3573 // first zip file entry is expected to be top level directory
3574 char *top_dir = zip_entries[0];
3576 // check if valid top level directory found in zip file
3577 if (!strSuffix(top_dir, "/"))
3580 // get path of extracted top level directory
3581 top_dir_path = getPath2(directory, top_dir);
3583 // remove trailing directory separator from top level directory path
3584 // (required to be able to check for file and directory in next step)
3585 top_dir_path[strlen(top_dir_path) - 1] = '\0';
3587 // check if zip file's top level directory already exists in target directory
3588 if (fileExists(top_dir_path)) // (checks for file and directory)
3591 // get filename of configuration file in top level directory
3592 top_dir_conf_filename = getStringCat2(top_dir, conf_basename);
3594 boolean found_top_dir_conf_filename = FALSE;
3597 while (zip_entries[i] != NULL)
3599 // check if every zip file entry is below top level directory
3600 if (!strPrefix(zip_entries[i], top_dir))
3603 // check if this zip file entry is the configuration filename
3604 if (strEqual(zip_entries[i], top_dir_conf_filename))
3605 found_top_dir_conf_filename = TRUE;
3610 // check if valid configuration filename was found in zip file
3611 if (!found_top_dir_conf_filename)
3617 char *ExtractZipFileIntoDirectory(char *zip_filename, char *directory,
3620 boolean zip_file_valid = CheckZipFileForDirectory(zip_filename, directory,
3623 if (!zip_file_valid)
3625 Warn("zip file '%s' rejected!", zip_filename);
3630 char **zip_entries = zip_extract(zip_filename, directory);
3632 if (zip_entries == NULL)
3634 Warn("zip file '%s' could not be extracted!", zip_filename);
3639 Info("zip file '%s' successfully extracted!", zip_filename);
3641 // first zip file entry contains top level directory
3642 char *top_dir = zip_entries[0];
3644 // remove trailing directory separator from top level directory
3645 top_dir[strlen(top_dir) - 1] = '\0';
3650 static void ProcessZipFilesInDirectory(char *directory, int tree_type)
3653 DirectoryEntry *dir_entry;
3655 if ((dir = openDirectory(directory)) == NULL)
3657 // display error if directory is main "options.graphics_directory" etc.
3658 if (tree_type == TREE_TYPE_LEVEL_DIR ||
3659 directory == OPTIONS_ARTWORK_DIRECTORY(tree_type))
3660 Warn("cannot read directory '%s'", directory);
3665 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3667 // skip non-zip files (and also directories with zip extension)
3668 if (!strSuffixLower(dir_entry->basename, ".zip") || dir_entry->is_directory)
3671 char *zip_filename = getPath2(directory, dir_entry->basename);
3672 char *zip_filename_extracted = getStringCat2(zip_filename, ".extracted");
3673 char *zip_filename_rejected = getStringCat2(zip_filename, ".rejected");
3675 // check if zip file hasn't already been extracted or rejected
3676 if (!fileExists(zip_filename_extracted) &&
3677 !fileExists(zip_filename_rejected))
3679 char *top_dir = ExtractZipFileIntoDirectory(zip_filename, directory,
3681 char *marker_filename = (top_dir != NULL ? zip_filename_extracted :
3682 zip_filename_rejected);
3685 // create empty file to mark zip file as extracted or rejected
3686 if ((marker_file = fopen(marker_filename, MODE_WRITE)))
3687 fclose(marker_file);
3690 free(zip_filename_extracted);
3691 free(zip_filename_rejected);
3695 closeDirectory(dir);
3698 // forward declaration for recursive call by "LoadLevelInfoFromLevelDir()"
3699 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
3701 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
3702 TreeInfo *node_parent,
3703 char *level_directory,
3704 char *directory_name)
3706 char *directory_path = getPath2(level_directory, directory_name);
3707 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
3708 SetupFileHash *setup_file_hash;
3709 LevelDirTree *leveldir_new = NULL;
3712 // unless debugging, silently ignore directories without "levelinfo.conf"
3713 if (!options.debug && !fileExists(filename))
3715 free(directory_path);
3721 setup_file_hash = loadSetupFileHash(filename);
3723 if (setup_file_hash == NULL)
3725 #if DEBUG_NO_CONFIG_FILE
3726 Debug("setup", "ignoring level directory '%s'", directory_path);
3729 free(directory_path);
3735 leveldir_new = newTreeInfo();
3738 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
3740 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
3742 leveldir_new->subdir = getStringCopy(directory_name);
3744 // set all structure fields according to the token/value pairs
3745 ldi = *leveldir_new;
3746 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
3747 setSetupInfo(levelinfo_tokens, i,
3748 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
3749 *leveldir_new = ldi;
3751 if (strEqual(leveldir_new->name, ANONYMOUS_NAME))
3752 setString(&leveldir_new->name, leveldir_new->subdir);
3754 if (leveldir_new->identifier == NULL)
3755 leveldir_new->identifier = getStringCopy(leveldir_new->subdir);
3757 if (leveldir_new->name_sorting == NULL)
3758 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
3760 if (node_parent == NULL) // top level group
3762 leveldir_new->basepath = getStringCopy(level_directory);
3763 leveldir_new->fullpath = getStringCopy(leveldir_new->subdir);
3765 else // sub level group
3767 leveldir_new->basepath = getStringCopy(node_parent->basepath);
3768 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
3771 leveldir_new->last_level =
3772 leveldir_new->first_level + leveldir_new->levels - 1;
3774 leveldir_new->in_user_dir =
3775 (!strEqual(leveldir_new->basepath, options.level_directory));
3777 // adjust some settings if user's private level directory was detected
3778 if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED &&
3779 leveldir_new->in_user_dir &&
3780 (strEqual(leveldir_new->subdir, getLoginName()) ||
3781 strEqual(leveldir_new->name, getLoginName()) ||
3782 strEqual(leveldir_new->author, getRealName())))
3784 leveldir_new->sort_priority = LEVELCLASS_PRIVATE_START;
3785 leveldir_new->readonly = FALSE;
3788 leveldir_new->user_defined =
3789 (leveldir_new->in_user_dir && IS_LEVELCLASS_PRIVATE(leveldir_new));
3791 setString(&leveldir_new->class_desc, getLevelClassDescription(leveldir_new));
3793 leveldir_new->handicap_level = // set handicap to default value
3794 (leveldir_new->user_defined || !leveldir_new->handicap ?
3795 leveldir_new->last_level : leveldir_new->first_level);
3797 DrawInitTextItem(leveldir_new->name);
3799 pushTreeInfo(node_first, leveldir_new);
3801 freeSetupFileHash(setup_file_hash);
3803 if (leveldir_new->level_group)
3805 // create node to link back to current level directory
3806 createParentTreeInfoNode(leveldir_new);
3808 // recursively step into sub-directory and look for more level series
3809 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
3810 leveldir_new, directory_path);
3813 free(directory_path);
3819 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
3820 TreeInfo *node_parent,
3821 char *level_directory)
3823 // ---------- 1st stage: process any level set zip files ----------
3825 ProcessZipFilesInDirectory(level_directory, TREE_TYPE_LEVEL_DIR);
3827 // ---------- 2nd stage: check for level set directories ----------
3830 DirectoryEntry *dir_entry;
3831 boolean valid_entry_found = FALSE;
3833 if ((dir = openDirectory(level_directory)) == NULL)
3835 Warn("cannot read level directory '%s'", level_directory);
3840 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
3842 char *directory_name = dir_entry->basename;
3843 char *directory_path = getPath2(level_directory, directory_name);
3845 // skip entries for current and parent directory
3846 if (strEqual(directory_name, ".") ||
3847 strEqual(directory_name, ".."))
3849 free(directory_path);
3854 // find out if directory entry is itself a directory
3855 if (!dir_entry->is_directory) // not a directory
3857 free(directory_path);
3862 free(directory_path);
3864 if (strEqual(directory_name, GRAPHICS_DIRECTORY) ||
3865 strEqual(directory_name, SOUNDS_DIRECTORY) ||
3866 strEqual(directory_name, MUSIC_DIRECTORY))
3869 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3874 closeDirectory(dir);
3876 // special case: top level directory may directly contain "levelinfo.conf"
3877 if (node_parent == NULL && !valid_entry_found)
3879 // check if this directory directly contains a file "levelinfo.conf"
3880 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
3881 level_directory, ".");
3884 boolean valid_entry_expected =
3885 (strEqual(level_directory, options.level_directory) ||
3886 setup.internal.create_user_levelset);
3888 if (valid_entry_expected && !valid_entry_found)
3889 Warn("cannot find any valid level series in directory '%s'",
3893 boolean AdjustGraphicsForEMC(void)
3895 boolean settings_changed = FALSE;
3897 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first_all);
3898 settings_changed |= adjustTreeGraphicsForEMC(leveldir_first);
3900 return settings_changed;
3903 boolean AdjustSoundsForEMC(void)
3905 boolean settings_changed = FALSE;
3907 settings_changed |= adjustTreeSoundsForEMC(leveldir_first_all);
3908 settings_changed |= adjustTreeSoundsForEMC(leveldir_first);
3910 return settings_changed;
3913 void LoadLevelInfo(void)
3915 InitUserLevelDirectory(getLoginName());
3917 DrawInitTextHead("Loading level series");
3919 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
3920 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
3922 leveldir_first = createTopTreeInfoNode(leveldir_first);
3924 /* after loading all level set information, clone the level directory tree
3925 and remove all level sets without levels (these may still contain artwork
3926 to be offered in the setup menu as "custom artwork", and are therefore
3927 checked for existing artwork in the function "LoadLevelArtworkInfo()") */
3928 leveldir_first_all = leveldir_first;
3929 cloneTree(&leveldir_first, leveldir_first_all, TRUE);
3931 AdjustGraphicsForEMC();
3932 AdjustSoundsForEMC();
3934 // before sorting, the first entries will be from the user directory
3935 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
3937 if (leveldir_first == NULL)
3938 Fail("cannot find any valid level series in any directory");
3940 sortTreeInfo(&leveldir_first);
3942 #if ENABLE_UNUSED_CODE
3943 dumpTreeInfo(leveldir_first, 0);
3947 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
3948 TreeInfo *node_parent,
3949 char *base_directory,
3950 char *directory_name, int type)
3952 char *directory_path = getPath2(base_directory, directory_name);
3953 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
3954 SetupFileHash *setup_file_hash = NULL;
3955 TreeInfo *artwork_new = NULL;
3958 if (fileExists(filename))
3959 setup_file_hash = loadSetupFileHash(filename);
3961 if (setup_file_hash == NULL) // no config file -- look for artwork files
3964 DirectoryEntry *dir_entry;
3965 boolean valid_file_found = FALSE;
3967 if ((dir = openDirectory(directory_path)) != NULL)
3969 while ((dir_entry = readDirectory(dir)) != NULL)
3971 if (FileIsArtworkType(dir_entry->filename, type))
3973 valid_file_found = TRUE;
3979 closeDirectory(dir);
3982 if (!valid_file_found)
3984 #if DEBUG_NO_CONFIG_FILE
3985 if (!strEqual(directory_name, "."))
3986 Debug("setup", "ignoring artwork directory '%s'", directory_path);
3989 free(directory_path);
3996 artwork_new = newTreeInfo();
3999 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4001 setTreeInfoToDefaults(artwork_new, type);
4003 artwork_new->subdir = getStringCopy(directory_name);
4005 if (setup_file_hash) // (before defining ".color" and ".class_desc")
4007 // set all structure fields according to the token/value pairs
4009 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4010 setSetupInfo(levelinfo_tokens, i,
4011 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
4014 if (strEqual(artwork_new->name, ANONYMOUS_NAME))
4015 setString(&artwork_new->name, artwork_new->subdir);
4017 if (artwork_new->identifier == NULL)
4018 artwork_new->identifier = getStringCopy(artwork_new->subdir);
4020 if (artwork_new->name_sorting == NULL)
4021 artwork_new->name_sorting = getStringCopy(artwork_new->name);
4024 if (node_parent == NULL) // top level group
4026 artwork_new->basepath = getStringCopy(base_directory);
4027 artwork_new->fullpath = getStringCopy(artwork_new->subdir);
4029 else // sub level group
4031 artwork_new->basepath = getStringCopy(node_parent->basepath);
4032 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
4035 artwork_new->in_user_dir =
4036 (!strEqual(artwork_new->basepath, OPTIONS_ARTWORK_DIRECTORY(type)));
4038 setString(&artwork_new->class_desc, getLevelClassDescription(artwork_new));
4040 if (setup_file_hash == NULL) // (after determining ".user_defined")
4042 if (strEqual(artwork_new->subdir, "."))
4044 if (artwork_new->user_defined)
4046 setString(&artwork_new->identifier, "private");
4047 artwork_new->sort_priority = ARTWORKCLASS_PRIVATE;
4051 setString(&artwork_new->identifier, "classic");
4052 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
4055 setString(&artwork_new->class_desc,
4056 getLevelClassDescription(artwork_new));
4060 setString(&artwork_new->identifier, artwork_new->subdir);
4063 setString(&artwork_new->name, artwork_new->identifier);
4064 setString(&artwork_new->name_sorting, artwork_new->name);
4067 pushTreeInfo(node_first, artwork_new);
4069 freeSetupFileHash(setup_file_hash);
4071 free(directory_path);
4077 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
4078 TreeInfo *node_parent,
4079 char *base_directory, int type)
4081 // ---------- 1st stage: process any artwork set zip files ----------
4083 ProcessZipFilesInDirectory(base_directory, type);
4085 // ---------- 2nd stage: check for artwork set directories ----------
4088 DirectoryEntry *dir_entry;
4089 boolean valid_entry_found = FALSE;
4091 if ((dir = openDirectory(base_directory)) == NULL)
4093 // display error if directory is main "options.graphics_directory" etc.
4094 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
4095 Warn("cannot read directory '%s'", base_directory);
4100 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
4102 char *directory_name = dir_entry->basename;
4103 char *directory_path = getPath2(base_directory, directory_name);
4105 // skip directory entries for current and parent directory
4106 if (strEqual(directory_name, ".") ||
4107 strEqual(directory_name, ".."))
4109 free(directory_path);
4114 // skip directory entries which are not a directory
4115 if (!dir_entry->is_directory) // not a directory
4117 free(directory_path);
4122 free(directory_path);
4124 // check if this directory contains artwork with or without config file
4125 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4127 directory_name, type);
4130 closeDirectory(dir);
4132 // check if this directory directly contains artwork itself
4133 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first, node_parent,
4134 base_directory, ".",
4136 if (!valid_entry_found)
4137 Warn("cannot find any valid artwork in directory '%s'", base_directory);
4140 static TreeInfo *getDummyArtworkInfo(int type)
4142 // this is only needed when there is completely no artwork available
4143 TreeInfo *artwork_new = newTreeInfo();
4145 setTreeInfoToDefaults(artwork_new, type);
4147 setString(&artwork_new->subdir, UNDEFINED_FILENAME);
4148 setString(&artwork_new->fullpath, UNDEFINED_FILENAME);
4149 setString(&artwork_new->basepath, UNDEFINED_FILENAME);
4151 setString(&artwork_new->identifier, UNDEFINED_FILENAME);
4152 setString(&artwork_new->name, UNDEFINED_FILENAME);
4153 setString(&artwork_new->name_sorting, UNDEFINED_FILENAME);
4158 void SetCurrentArtwork(int type)
4160 ArtworkDirTree **current_ptr = ARTWORK_CURRENT_PTR(artwork, type);
4161 ArtworkDirTree *first_node = ARTWORK_FIRST_NODE(artwork, type);
4162 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4163 char *default_subdir = ARTWORK_DEFAULT_SUBDIR(type);
4165 // set current artwork to artwork configured in setup menu
4166 *current_ptr = getTreeInfoFromIdentifier(first_node, setup_set);
4168 // if not found, set current artwork to default artwork
4169 if (*current_ptr == NULL)
4170 *current_ptr = getTreeInfoFromIdentifier(first_node, default_subdir);
4172 // if not found, set current artwork to first artwork in tree
4173 if (*current_ptr == NULL)
4174 *current_ptr = getFirstValidTreeInfoEntry(first_node);
4177 void ChangeCurrentArtworkIfNeeded(int type)
4179 char *current_identifier = ARTWORK_CURRENT_IDENTIFIER(artwork, type);
4180 char *setup_set = SETUP_ARTWORK_SET(setup, type);
4182 if (!strEqual(current_identifier, setup_set))
4183 SetCurrentArtwork(type);
4186 void LoadArtworkInfo(void)
4188 LoadArtworkInfoCache();
4190 DrawInitTextHead("Looking for custom artwork");
4192 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4193 options.graphics_directory,
4194 TREE_TYPE_GRAPHICS_DIR);
4195 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
4196 getUserGraphicsDir(),
4197 TREE_TYPE_GRAPHICS_DIR);
4199 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4200 options.sounds_directory,
4201 TREE_TYPE_SOUNDS_DIR);
4202 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
4204 TREE_TYPE_SOUNDS_DIR);
4206 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4207 options.music_directory,
4208 TREE_TYPE_MUSIC_DIR);
4209 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
4211 TREE_TYPE_MUSIC_DIR);
4213 if (artwork.gfx_first == NULL)
4214 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
4215 if (artwork.snd_first == NULL)
4216 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
4217 if (artwork.mus_first == NULL)
4218 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
4220 // before sorting, the first entries will be from the user directory
4221 SetCurrentArtwork(ARTWORK_TYPE_GRAPHICS);
4222 SetCurrentArtwork(ARTWORK_TYPE_SOUNDS);
4223 SetCurrentArtwork(ARTWORK_TYPE_MUSIC);
4225 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
4226 artwork.snd_current_identifier = artwork.snd_current->identifier;
4227 artwork.mus_current_identifier = artwork.mus_current->identifier;
4229 #if ENABLE_UNUSED_CODE
4230 Debug("setup:LoadArtworkInfo", "graphics set == %s",
4231 artwork.gfx_current_identifier);
4232 Debug("setup:LoadArtworkInfo", "sounds set == %s",
4233 artwork.snd_current_identifier);
4234 Debug("setup:LoadArtworkInfo", "music set == %s",
4235 artwork.mus_current_identifier);
4238 sortTreeInfo(&artwork.gfx_first);
4239 sortTreeInfo(&artwork.snd_first);
4240 sortTreeInfo(&artwork.mus_first);
4242 #if ENABLE_UNUSED_CODE
4243 dumpTreeInfo(artwork.gfx_first, 0);
4244 dumpTreeInfo(artwork.snd_first, 0);
4245 dumpTreeInfo(artwork.mus_first, 0);
4249 static void MoveArtworkInfoIntoSubTree(ArtworkDirTree **artwork_node)
4251 ArtworkDirTree *artwork_new = newTreeInfo();
4252 char *top_node_name = "standalone artwork";
4254 setTreeInfoToDefaults(artwork_new, (*artwork_node)->type);
4256 artwork_new->level_group = TRUE;
4258 setString(&artwork_new->identifier, top_node_name);
4259 setString(&artwork_new->name, top_node_name);
4260 setString(&artwork_new->name_sorting, top_node_name);
4262 // create node to link back to current custom artwork directory
4263 createParentTreeInfoNode(artwork_new);
4265 // move existing custom artwork tree into newly created sub-tree
4266 artwork_new->node_group->next = *artwork_node;
4268 // change custom artwork tree to contain only newly created node
4269 *artwork_node = artwork_new;
4272 static void LoadArtworkInfoFromLevelInfoExt(ArtworkDirTree **artwork_node,
4273 ArtworkDirTree *node_parent,
4274 LevelDirTree *level_node,
4275 boolean empty_level_set_mode)
4277 int type = (*artwork_node)->type;
4279 // recursively check all level directories for artwork sub-directories
4283 boolean empty_level_set = (level_node->levels == 0);
4285 // check all tree entries for artwork, but skip parent link entries
4286 if (!level_node->parent_link && empty_level_set == empty_level_set_mode)
4288 TreeInfo *artwork_new = getArtworkInfoCacheEntry(level_node, type);
4289 boolean cached = (artwork_new != NULL);
4293 pushTreeInfo(artwork_node, artwork_new);
4297 TreeInfo *topnode_last = *artwork_node;
4298 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
4299 ARTWORK_DIRECTORY(type));
4301 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path, type);
4303 if (topnode_last != *artwork_node) // check for newly added node
4305 artwork_new = *artwork_node;
4307 setString(&artwork_new->identifier, level_node->subdir);
4308 setString(&artwork_new->name, level_node->name);
4309 setString(&artwork_new->name_sorting, level_node->name_sorting);
4311 artwork_new->sort_priority = level_node->sort_priority;
4312 artwork_new->in_user_dir = level_node->in_user_dir;
4314 update_artworkinfo_cache = TRUE;
4320 // insert artwork info (from old cache or filesystem) into new cache
4321 if (artwork_new != NULL)
4322 setArtworkInfoCacheEntry(artwork_new, level_node, type);
4325 DrawInitTextItem(level_node->name);
4327 if (level_node->node_group != NULL)
4329 TreeInfo *artwork_new = newTreeInfo();
4332 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
4334 setTreeInfoToDefaults(artwork_new, type);
4336 artwork_new->level_group = TRUE;
4338 setString(&artwork_new->identifier, level_node->subdir);
4340 if (node_parent == NULL) // check for top tree node
4342 char *top_node_name = (empty_level_set_mode ?
4343 "artwork for certain level sets" :
4344 "artwork included in level sets");
4346 setString(&artwork_new->name, top_node_name);
4347 setString(&artwork_new->name_sorting, top_node_name);
4351 setString(&artwork_new->name, level_node->name);
4352 setString(&artwork_new->name_sorting, level_node->name_sorting);
4355 pushTreeInfo(artwork_node, artwork_new);
4357 // create node to link back to current custom artwork directory
4358 createParentTreeInfoNode(artwork_new);
4360 // recursively step into sub-directory and look for more custom artwork
4361 LoadArtworkInfoFromLevelInfoExt(&artwork_new->node_group, artwork_new,
4362 level_node->node_group,
4363 empty_level_set_mode);
4365 // if sub-tree has no custom artwork at all, remove it
4366 if (artwork_new->node_group->next == NULL)
4367 removeTreeInfo(artwork_node);
4370 level_node = level_node->next;
4374 static void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node)
4376 // move peviously loaded artwork tree into separate sub-tree
4377 MoveArtworkInfoIntoSubTree(artwork_node);
4379 // load artwork from level sets into separate sub-trees
4380 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, TRUE);
4381 LoadArtworkInfoFromLevelInfoExt(artwork_node, NULL, leveldir_first_all, FALSE);
4383 // add top tree node over all sub-trees and set parent links
4384 *artwork_node = addTopTreeInfoNode(*artwork_node);
4387 void LoadLevelArtworkInfo(void)
4389 print_timestamp_init("LoadLevelArtworkInfo");
4391 DrawInitTextHead("Looking for custom level artwork");
4393 print_timestamp_time("DrawTimeText");
4395 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first);
4396 print_timestamp_time("LoadArtworkInfoFromLevelInfo (gfx)");
4397 LoadArtworkInfoFromLevelInfo(&artwork.snd_first);
4398 print_timestamp_time("LoadArtworkInfoFromLevelInfo (snd)");
4399 LoadArtworkInfoFromLevelInfo(&artwork.mus_first);
4400 print_timestamp_time("LoadArtworkInfoFromLevelInfo (mus)");
4402 SaveArtworkInfoCache();
4404 print_timestamp_time("SaveArtworkInfoCache");
4406 // needed for reloading level artwork not known at ealier stage
4407 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_GRAPHICS);
4408 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_SOUNDS);
4409 ChangeCurrentArtworkIfNeeded(ARTWORK_TYPE_MUSIC);
4411 print_timestamp_time("getTreeInfoFromIdentifier");
4413 sortTreeInfo(&artwork.gfx_first);
4414 sortTreeInfo(&artwork.snd_first);
4415 sortTreeInfo(&artwork.mus_first);
4417 print_timestamp_time("sortTreeInfo");
4419 #if ENABLE_UNUSED_CODE
4420 dumpTreeInfo(artwork.gfx_first, 0);
4421 dumpTreeInfo(artwork.snd_first, 0);
4422 dumpTreeInfo(artwork.mus_first, 0);
4425 print_timestamp_done("LoadLevelArtworkInfo");
4428 static boolean AddTreeSetToTreeInfoExt(TreeInfo *tree_node_old, char *tree_dir,
4429 char *tree_subdir_new, int type)
4431 if (tree_node_old == NULL)
4433 if (type == TREE_TYPE_LEVEL_DIR)
4435 // get level info tree node of personal user level set
4436 tree_node_old = getTreeInfoFromIdentifier(leveldir_first, getLoginName());
4438 // this may happen if "setup.internal.create_user_levelset" is FALSE
4439 // or if file "levelinfo.conf" is missing in personal user level set
4440 if (tree_node_old == NULL)
4441 tree_node_old = leveldir_first->node_group;
4445 // get artwork info tree node of first artwork set
4446 tree_node_old = ARTWORK_FIRST_NODE(artwork, type);
4450 if (tree_dir == NULL)
4451 tree_dir = TREE_USERDIR(type);
4453 if (tree_node_old == NULL ||
4455 tree_subdir_new == NULL) // should not happen
4458 int draw_deactivation_mask = GetDrawDeactivationMask();
4460 // override draw deactivation mask (temporarily disable drawing)
4461 SetDrawDeactivationMask(REDRAW_ALL);
4463 if (type == TREE_TYPE_LEVEL_DIR)
4465 // load new level set config and add it next to first user level set
4466 LoadLevelInfoFromLevelConf(&tree_node_old->next,
4467 tree_node_old->node_parent,
4468 tree_dir, tree_subdir_new);
4472 // load new artwork set config and add it next to first artwork set
4473 LoadArtworkInfoFromArtworkConf(&tree_node_old->next,
4474 tree_node_old->node_parent,
4475 tree_dir, tree_subdir_new, type);
4478 // set draw deactivation mask to previous value
4479 SetDrawDeactivationMask(draw_deactivation_mask);
4481 // get first node of level or artwork info tree
4482 TreeInfo **tree_node_first = TREE_FIRST_NODE_PTR(type);
4484 // get tree info node of newly added level or artwork set
4485 TreeInfo *tree_node_new = getTreeInfoFromIdentifier(*tree_node_first,
4488 if (tree_node_new == NULL) // should not happen
4491 // correct top link and parent node link of newly created tree node
4492 tree_node_new->node_top = tree_node_old->node_top;
4493 tree_node_new->node_parent = tree_node_old->node_parent;
4495 // sort tree info to adjust position of newly added tree set
4496 sortTreeInfo(tree_node_first);
4501 void AddTreeSetToTreeInfo(TreeInfo *tree_node, char *tree_dir,
4502 char *tree_subdir_new, int type)
4504 if (!AddTreeSetToTreeInfoExt(tree_node, tree_dir, tree_subdir_new, type))
4505 Fail("internal tree info structure corrupted -- aborting");
4508 void AddUserLevelSetToLevelInfo(char *level_subdir_new)
4510 AddTreeSetToTreeInfo(NULL, NULL, level_subdir_new, TREE_TYPE_LEVEL_DIR);
4513 char *getArtworkIdentifierForUserLevelSet(int type)
4515 char *classic_artwork_set = getClassicArtworkSet(type);
4517 // check for custom artwork configured in "levelinfo.conf"
4518 char *leveldir_artwork_set =
4519 *LEVELDIR_ARTWORK_SET_PTR(leveldir_current, type);
4520 boolean has_leveldir_artwork_set =
4521 (leveldir_artwork_set != NULL && !strEqual(leveldir_artwork_set,
4522 classic_artwork_set));
4524 // check for custom artwork in sub-directory "graphics" etc.
4525 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4526 char *leveldir_identifier = leveldir_current->identifier;
4527 boolean has_artwork_subdir =
4528 (getTreeInfoFromIdentifier(artwork_first_node,
4529 leveldir_identifier) != NULL);
4531 return (has_leveldir_artwork_set ? leveldir_artwork_set :
4532 has_artwork_subdir ? leveldir_identifier :
4533 classic_artwork_set);
4536 TreeInfo *getArtworkTreeInfoForUserLevelSet(int type)
4538 char *artwork_set = getArtworkIdentifierForUserLevelSet(type);
4539 TreeInfo *artwork_first_node = ARTWORK_FIRST_NODE(artwork, type);
4540 TreeInfo *ti = getTreeInfoFromIdentifier(artwork_first_node, artwork_set);
4544 ti = getTreeInfoFromIdentifier(artwork_first_node,
4545 ARTWORK_DEFAULT_SUBDIR(type));
4547 Fail("cannot find default graphics -- should not happen");
4553 boolean checkIfCustomArtworkExistsForCurrentLevelSet(void)
4555 char *graphics_set =
4556 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS);
4558 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS);
4560 getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC);
4562 return (!strEqual(graphics_set, GFX_CLASSIC_SUBDIR) ||
4563 !strEqual(sounds_set, SND_CLASSIC_SUBDIR) ||
4564 !strEqual(music_set, MUS_CLASSIC_SUBDIR));
4567 boolean UpdateUserLevelSet(char *level_subdir, char *level_name,
4568 char *level_author, int num_levels)
4570 char *filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4571 char *filename_tmp = getStringCat2(filename, ".tmp");
4573 FILE *file_tmp = NULL;
4574 char line[MAX_LINE_LEN];
4575 boolean success = FALSE;
4576 LevelDirTree *leveldir = getTreeInfoFromIdentifier(leveldir_first,
4578 // update values in level directory tree
4580 if (level_name != NULL)
4581 setString(&leveldir->name, level_name);
4583 if (level_author != NULL)
4584 setString(&leveldir->author, level_author);
4586 if (num_levels != -1)
4587 leveldir->levels = num_levels;
4589 // update values that depend on other values
4591 setString(&leveldir->name_sorting, leveldir->name);
4593 leveldir->last_level = leveldir->first_level + leveldir->levels - 1;
4595 // sort order of level sets may have changed
4596 sortTreeInfo(&leveldir_first);
4598 if ((file = fopen(filename, MODE_READ)) &&
4599 (file_tmp = fopen(filename_tmp, MODE_WRITE)))
4601 while (fgets(line, MAX_LINE_LEN, file))
4603 if (strPrefix(line, "name:") && level_name != NULL)
4604 fprintf(file_tmp, "%-32s%s\n", "name:", level_name);
4605 else if (strPrefix(line, "author:") && level_author != NULL)
4606 fprintf(file_tmp, "%-32s%s\n", "author:", level_author);
4607 else if (strPrefix(line, "levels:") && num_levels != -1)
4608 fprintf(file_tmp, "%-32s%d\n", "levels:", num_levels);
4610 fputs(line, file_tmp);
4623 success = (rename(filename_tmp, filename) == 0);
4631 boolean CreateUserLevelSet(char *level_subdir, char *level_name,
4632 char *level_author, int num_levels,
4633 boolean use_artwork_set)
4635 LevelDirTree *level_info;
4640 // create user level sub-directory, if needed
4641 createDirectory(getUserLevelDir(level_subdir), "user level");
4643 filename = getPath2(getUserLevelDir(level_subdir), LEVELINFO_FILENAME);
4645 if (!(file = fopen(filename, MODE_WRITE)))
4647 Warn("cannot write level info file '%s'", filename);
4654 level_info = newTreeInfo();
4656 // always start with reliable default values
4657 setTreeInfoToDefaults(level_info, TREE_TYPE_LEVEL_DIR);
4659 setString(&level_info->name, level_name);
4660 setString(&level_info->author, level_author);
4661 level_info->levels = num_levels;
4662 level_info->first_level = 1;
4663 level_info->sort_priority = LEVELCLASS_PRIVATE_START;
4664 level_info->readonly = FALSE;
4666 if (use_artwork_set)
4668 level_info->graphics_set =
4669 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_GRAPHICS));
4670 level_info->sounds_set =
4671 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_SOUNDS));
4672 level_info->music_set =
4673 getStringCopy(getArtworkIdentifierForUserLevelSet(ARTWORK_TYPE_MUSIC));
4676 token_value_position = TOKEN_VALUE_POSITION_SHORT;
4678 fprintFileHeader(file, LEVELINFO_FILENAME);
4681 for (i = 0; i < NUM_LEVELINFO_TOKENS; i++)
4683 if (i == LEVELINFO_TOKEN_NAME ||
4684 i == LEVELINFO_TOKEN_AUTHOR ||
4685 i == LEVELINFO_TOKEN_LEVELS ||
4686 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4687 i == LEVELINFO_TOKEN_SORT_PRIORITY ||
4688 i == LEVELINFO_TOKEN_READONLY ||
4689 (use_artwork_set && (i == LEVELINFO_TOKEN_GRAPHICS_SET ||
4690 i == LEVELINFO_TOKEN_SOUNDS_SET ||
4691 i == LEVELINFO_TOKEN_MUSIC_SET)))
4692 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
4694 // just to make things nicer :)
4695 if (i == LEVELINFO_TOKEN_AUTHOR ||
4696 i == LEVELINFO_TOKEN_FIRST_LEVEL ||
4697 (use_artwork_set && i == LEVELINFO_TOKEN_READONLY))
4698 fprintf(file, "\n");
4701 token_value_position = TOKEN_VALUE_POSITION_DEFAULT;
4705 SetFilePermissions(filename, PERMS_PRIVATE);
4707 freeTreeInfo(level_info);
4713 static void SaveUserLevelInfo(void)
4715 CreateUserLevelSet(getLoginName(), getLoginName(), getRealName(), 100, FALSE);
4718 char *getSetupValue(int type, void *value)
4720 static char value_string[MAX_LINE_LEN];
4728 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
4732 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
4736 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4737 *(int *)value == FALSE ? "off" : "on"));
4741 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
4744 case TYPE_YES_NO_AUTO:
4745 strcpy(value_string, (*(int *)value == AUTO ? "auto" :
4746 *(int *)value == FALSE ? "no" : "yes"));
4750 strcpy(value_string, (*(boolean *)value ? "AGA" : "ECS"));
4754 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
4758 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
4762 sprintf(value_string, "%d", *(int *)value);
4766 if (*(char **)value == NULL)
4769 strcpy(value_string, *(char **)value);
4773 sprintf(value_string, "player_%d", *(int *)value + 1);
4777 value_string[0] = '\0';
4781 if (type & TYPE_GHOSTED)
4782 strcpy(value_string, "n/a");
4784 return value_string;
4787 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
4791 static char token_string[MAX_LINE_LEN];
4792 int token_type = token_info[token_nr].type;
4793 void *setup_value = token_info[token_nr].value;
4794 char *token_text = token_info[token_nr].text;
4795 char *value_string = getSetupValue(token_type, setup_value);
4797 // build complete token string
4798 sprintf(token_string, "%s%s", prefix, token_text);
4800 // build setup entry line
4801 line = getFormattedSetupEntry(token_string, value_string);
4803 if (token_type == TYPE_KEY_X11)
4805 Key key = *(Key *)setup_value;
4806 char *keyname = getKeyNameFromKey(key);
4808 // add comment, if useful
4809 if (!strEqual(keyname, "(undefined)") &&
4810 !strEqual(keyname, "(unknown)"))
4812 // add at least one whitespace
4814 for (i = strlen(line); i < token_comment_position; i++)
4818 strcat(line, keyname);
4825 static void InitLastPlayedLevels_ParentNode(void)
4827 LevelDirTree **leveldir_top = &leveldir_first->node_group->next;
4828 LevelDirTree *leveldir_new = NULL;
4830 // check if parent node for last played levels already exists
4831 if (strEqual((*leveldir_top)->identifier, TOKEN_STR_LAST_LEVEL_SERIES))
4834 leveldir_new = newTreeInfo();
4836 setTreeInfoToDefaultsFromParent(leveldir_new, leveldir_first);
4838 leveldir_new->level_group = TRUE;
4839 leveldir_new->sort_priority = LEVELCLASS_LAST_PLAYED_LEVEL;
4841 setString(&leveldir_new->identifier, TOKEN_STR_LAST_LEVEL_SERIES);
4842 setString(&leveldir_new->name, "<< (last played level sets)");
4843 setString(&leveldir_new->name_sorting, leveldir_new->name);
4845 pushTreeInfo(leveldir_top, leveldir_new);
4847 // create node to link back to current level directory
4848 createParentTreeInfoNode(leveldir_new);
4851 void UpdateLastPlayedLevels_TreeInfo(void)
4853 char **last_level_series = setup.level_setup.last_level_series;
4854 LevelDirTree *leveldir_last;
4855 TreeInfo **node_new = NULL;
4858 if (last_level_series[0] == NULL)
4861 InitLastPlayedLevels_ParentNode();
4863 leveldir_last = getTreeInfoFromIdentifierExt(leveldir_first,
4864 TOKEN_STR_LAST_LEVEL_SERIES,
4865 TREE_NODE_TYPE_GROUP);
4866 if (leveldir_last == NULL)
4869 node_new = &leveldir_last->node_group->next;
4871 freeTreeInfo(*node_new);
4875 for (i = 0; last_level_series[i] != NULL; i++)
4877 LevelDirTree *node_last = getTreeInfoFromIdentifier(leveldir_first,
4878 last_level_series[i]);
4879 if (node_last == NULL)
4882 *node_new = getTreeInfoCopy(node_last); // copy complete node
4884 (*node_new)->node_top = &leveldir_first; // correct top node link
4885 (*node_new)->node_parent = leveldir_last; // correct parent node link
4887 (*node_new)->is_copy = TRUE; // mark entry as node copy
4889 (*node_new)->node_group = NULL;
4890 (*node_new)->next = NULL;
4892 (*node_new)->cl_first = -1; // force setting tree cursor
4894 node_new = &((*node_new)->next);
4898 static void UpdateLastPlayedLevels_List(void)
4900 char **last_level_series = setup.level_setup.last_level_series;
4901 int pos = MAX_LEVELDIR_HISTORY - 1;
4904 // search for potentially already existing entry in list of level sets
4905 for (i = 0; last_level_series[i] != NULL; i++)
4906 if (strEqual(last_level_series[i], leveldir_current->identifier))
4909 // move list of level sets one entry down (using potentially free entry)
4910 for (i = pos; i > 0; i--)
4911 setString(&last_level_series[i], last_level_series[i - 1]);
4913 // put last played level set at top position
4914 setString(&last_level_series[0], leveldir_current->identifier);
4917 static TreeInfo *StoreOrRestoreLastPlayedLevels(TreeInfo *node, boolean store)
4919 static char *identifier = NULL;
4923 setString(&identifier, (node && node->is_copy ? node->identifier : NULL));
4925 return NULL; // not used
4929 TreeInfo *node_new = getTreeInfoFromIdentifierExt(leveldir_first,
4931 TREE_NODE_TYPE_COPY);
4932 return (node_new != NULL ? node_new : node);
4936 void StoreLastPlayedLevels(TreeInfo *node)
4938 StoreOrRestoreLastPlayedLevels(node, TRUE);
4941 void RestoreLastPlayedLevels(TreeInfo **node)
4943 *node = StoreOrRestoreLastPlayedLevels(*node, FALSE);
4946 void LoadLevelSetup_LastSeries(void)
4948 // --------------------------------------------------------------------------
4949 // ~/.<program>/levelsetup.conf
4950 // --------------------------------------------------------------------------
4952 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
4953 SetupFileHash *level_setup_hash = NULL;
4957 // always start with reliable default values
4958 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4960 // start with empty history of last played level sets
4961 setString(&setup.level_setup.last_level_series[0], NULL);
4963 if (!strEqual(DEFAULT_LEVELSET, UNDEFINED_LEVELSET))
4965 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4967 if (leveldir_current == NULL)
4968 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4971 if ((level_setup_hash = loadSetupFileHash(filename)))
4973 char *last_level_series =
4974 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
4976 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
4978 if (leveldir_current == NULL)
4979 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
4981 for (i = 0; i < MAX_LEVELDIR_HISTORY; i++)
4983 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 10];
4984 LevelDirTree *leveldir_last;
4986 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
4988 last_level_series = getHashEntry(level_setup_hash, token);
4990 leveldir_last = getTreeInfoFromIdentifier(leveldir_first,
4992 if (leveldir_last != NULL)
4993 setString(&setup.level_setup.last_level_series[pos++],
4997 setString(&setup.level_setup.last_level_series[pos], NULL);
4999 freeSetupFileHash(level_setup_hash);
5003 Debug("setup", "using default setup values");
5009 static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series)
5011 // --------------------------------------------------------------------------
5012 // ~/.<program>/levelsetup.conf
5013 // --------------------------------------------------------------------------
5015 // check if the current level directory structure is available at this point
5016 if (leveldir_current == NULL)
5019 char **last_level_series = setup.level_setup.last_level_series;
5020 char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
5024 InitUserDataDirectory();
5026 UpdateLastPlayedLevels_List();
5028 if (!(file = fopen(filename, MODE_WRITE)))
5030 Warn("cannot write setup file '%s'", filename);
5037 fprintFileHeader(file, LEVELSETUP_FILENAME);
5039 if (deactivate_last_level_series)
5040 fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated");
5042 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
5043 leveldir_current->identifier));
5045 for (i = 0; last_level_series[i] != NULL; i++)
5047 char token[strlen(TOKEN_STR_LAST_LEVEL_SERIES) + 1 + 10 + 1];
5049 sprintf(token, "%s.%03d", TOKEN_STR_LAST_LEVEL_SERIES, i);
5051 fprintf(file, "%s\n", getFormattedSetupEntry(token, last_level_series[i]));
5056 SetFilePermissions(filename, PERMS_PRIVATE);
5061 void SaveLevelSetup_LastSeries(void)
5063 SaveLevelSetup_LastSeries_Ext(FALSE);
5066 void SaveLevelSetup_LastSeries_Deactivate(void)
5068 SaveLevelSetup_LastSeries_Ext(TRUE);
5071 static void checkSeriesInfo(void)
5073 static char *level_directory = NULL;
5076 DirectoryEntry *dir_entry;
5079 checked_free(level_directory);
5081 // check for more levels besides the 'levels' field of 'levelinfo.conf'
5083 level_directory = getPath2((leveldir_current->in_user_dir ?
5084 getUserLevelDir(NULL) :
5085 options.level_directory),
5086 leveldir_current->fullpath);
5088 if ((dir = openDirectory(level_directory)) == NULL)
5090 Warn("cannot read level directory '%s'", level_directory);
5096 while ((dir_entry = readDirectory(dir)) != NULL) // loop all entries
5098 if (strlen(dir_entry->basename) > 4 &&
5099 dir_entry->basename[3] == '.' &&
5100 strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION))
5102 char levelnum_str[4];
5105 strncpy(levelnum_str, dir_entry->basename, 3);
5106 levelnum_str[3] = '\0';
5108 levelnum_value = atoi(levelnum_str);
5110 if (levelnum_value < leveldir_current->first_level)
5112 Warn("additional level %d found", levelnum_value);
5114 leveldir_current->first_level = levelnum_value;
5116 else if (levelnum_value > leveldir_current->last_level)
5118 Warn("additional level %d found", levelnum_value);
5120 leveldir_current->last_level = levelnum_value;
5126 closeDirectory(dir);
5129 void LoadLevelSetup_SeriesInfo(void)
5132 SetupFileHash *level_setup_hash = NULL;
5133 char *level_subdir = leveldir_current->subdir;
5136 // always start with reliable default values
5137 level_nr = leveldir_current->first_level;
5139 for (i = 0; i < MAX_LEVELS; i++)
5141 LevelStats_setPlayed(i, 0);
5142 LevelStats_setSolved(i, 0);
5147 // --------------------------------------------------------------------------
5148 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5149 // --------------------------------------------------------------------------
5151 level_subdir = leveldir_current->subdir;
5153 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5155 if ((level_setup_hash = loadSetupFileHash(filename)))
5159 // get last played level in this level set
5161 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
5165 level_nr = atoi(token_value);
5167 if (level_nr < leveldir_current->first_level)
5168 level_nr = leveldir_current->first_level;
5169 if (level_nr > leveldir_current->last_level)
5170 level_nr = leveldir_current->last_level;
5173 // get handicap level in this level set
5175 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
5179 int level_nr = atoi(token_value);
5181 if (level_nr < leveldir_current->first_level)
5182 level_nr = leveldir_current->first_level;
5183 if (level_nr > leveldir_current->last_level + 1)
5184 level_nr = leveldir_current->last_level;
5186 if (leveldir_current->user_defined || !leveldir_current->handicap)
5187 level_nr = leveldir_current->last_level;
5189 leveldir_current->handicap_level = level_nr;
5192 // get number of played and solved levels in this level set
5194 BEGIN_HASH_ITERATION(level_setup_hash, itr)
5196 char *token = HASH_ITERATION_TOKEN(itr);
5197 char *value = HASH_ITERATION_VALUE(itr);
5199 if (strlen(token) == 3 &&
5200 token[0] >= '0' && token[0] <= '9' &&
5201 token[1] >= '0' && token[1] <= '9' &&
5202 token[2] >= '0' && token[2] <= '9')
5204 int level_nr = atoi(token);
5207 LevelStats_setPlayed(level_nr, atoi(value)); // read 1st column
5209 value = strchr(value, ' ');
5212 LevelStats_setSolved(level_nr, atoi(value)); // read 2nd column
5215 END_HASH_ITERATION(hash, itr)
5217 freeSetupFileHash(level_setup_hash);
5221 Debug("setup", "using default setup values");
5227 void SaveLevelSetup_SeriesInfo(void)
5230 char *level_subdir = leveldir_current->subdir;
5231 char *level_nr_str = int2str(level_nr, 0);
5232 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
5236 // --------------------------------------------------------------------------
5237 // ~/.<program>/levelsetup/<level series>/levelsetup.conf
5238 // --------------------------------------------------------------------------
5240 InitLevelSetupDirectory(level_subdir);
5242 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
5244 if (!(file = fopen(filename, MODE_WRITE)))
5246 Warn("cannot write setup file '%s'", filename);
5253 fprintFileHeader(file, LEVELSETUP_FILENAME);
5255 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
5257 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
5258 handicap_level_str));
5260 for (i = leveldir_current->first_level; i <= leveldir_current->last_level;
5263 if (LevelStats_getPlayed(i) > 0 ||
5264 LevelStats_getSolved(i) > 0)
5269 sprintf(token, "%03d", i);
5270 sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i));
5272 fprintf(file, "%s\n", getFormattedSetupEntry(token, value));
5278 SetFilePermissions(filename, PERMS_PRIVATE);
5283 int LevelStats_getPlayed(int nr)
5285 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0);
5288 int LevelStats_getSolved(int nr)
5290 return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0);
5293 void LevelStats_setPlayed(int nr, int value)
5295 if (nr >= 0 && nr < MAX_LEVELS)
5296 level_stats[nr].played = value;
5299 void LevelStats_setSolved(int nr, int value)
5301 if (nr >= 0 && nr < MAX_LEVELS)
5302 level_stats[nr].solved = value;
5305 void LevelStats_incPlayed(int nr)
5307 if (nr >= 0 && nr < MAX_LEVELS)
5308 level_stats[nr].played++;
5311 void LevelStats_incSolved(int nr)
5313 if (nr >= 0 && nr < MAX_LEVELS)
5314 level_stats[nr].solved++;
5317 void LoadUserSetup(void)
5319 // --------------------------------------------------------------------------
5320 // ~/.<program>/usersetup.conf
5321 // --------------------------------------------------------------------------
5323 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5324 SetupFileHash *user_setup_hash = NULL;
5326 // always start with reliable default values
5329 if ((user_setup_hash = loadSetupFileHash(filename)))
5333 // get last selected user number
5334 token_value = getHashEntry(user_setup_hash, TOKEN_STR_LAST_USER);
5337 user.nr = MIN(MAX(0, atoi(token_value)), MAX_PLAYER_NAMES - 1);
5339 freeSetupFileHash(user_setup_hash);
5343 Debug("setup", "using default setup values");
5349 void SaveUserSetup(void)
5351 // --------------------------------------------------------------------------
5352 // ~/.<program>/usersetup.conf
5353 // --------------------------------------------------------------------------
5355 char *filename = getPath2(getMainUserGameDataDir(), USERSETUP_FILENAME);
5358 InitMainUserDataDirectory();
5360 if (!(file = fopen(filename, MODE_WRITE)))
5362 Warn("cannot write setup file '%s'", filename);
5369 fprintFileHeader(file, USERSETUP_FILENAME);
5371 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_USER,
5375 SetFilePermissions(filename, PERMS_PRIVATE);