1 /***********************************************************
2 * Artsoft Retro-Game Library *
3 *----------------------------------------------------------*
4 * (c) 1994-2002 Artsoft Entertainment *
6 * Detmolder Strasse 189 *
9 * e-mail: info@artsoft.org *
10 *----------------------------------------------------------*
12 ***********************************************************/
14 #include <sys/types.h>
26 /* file names and filename extensions */
27 #if !defined(PLATFORM_MSDOS)
28 #define LEVELSETUP_DIRECTORY "levelsetup"
29 #define SETUP_FILENAME "setup.conf"
30 #define LEVELSETUP_FILENAME "levelsetup.conf"
31 #define LEVELINFO_FILENAME "levelinfo.conf"
32 #define GRAPHICSINFO_FILENAME "graphicsinfo.conf"
33 #define SOUNDSINFO_FILENAME "soundsinfo.conf"
34 #define MUSICINFO_FILENAME "musicinfo.conf"
35 #define LEVELFILE_EXTENSION "level"
36 #define TAPEFILE_EXTENSION "tape"
37 #define SCOREFILE_EXTENSION "score"
39 #define LEVELSETUP_DIRECTORY "lvlsetup"
40 #define SETUP_FILENAME "setup.cnf"
41 #define LEVELSETUP_FILENAME "lvlsetup.cnf"
42 #define LEVELINFO_FILENAME "lvlinfo.cnf"
43 #define GRAPHICSINFO_FILENAME "gfxinfo.cnf"
44 #define SOUNDSINFO_FILENAME "sndinfo.cnf"
45 #define MUSICINFO_FILENAME "musinfo.cnf"
46 #define LEVELFILE_EXTENSION "lvl"
47 #define TAPEFILE_EXTENSION "tap"
48 #define SCOREFILE_EXTENSION "sco"
51 #define NUM_LEVELCLASS_DESC 8
52 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
64 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
65 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
66 IS_LEVELCLASS_BD(n) ? FC_GREEN : \
67 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
68 IS_LEVELCLASS_SP(n) ? FC_GREEN : \
69 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
70 IS_LEVELCLASS_CONTRIBUTION(n) ? FC_GREEN : \
71 IS_LEVELCLASS_USER(n) ? FC_RED : \
74 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
75 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
76 IS_LEVELCLASS_BD(n) ? 2 : \
77 IS_LEVELCLASS_EM(n) ? 3 : \
78 IS_LEVELCLASS_SP(n) ? 4 : \
79 IS_LEVELCLASS_DX(n) ? 5 : \
80 IS_LEVELCLASS_CONTRIBUTION(n) ? 6 : \
81 IS_LEVELCLASS_USER(n) ? 7 : \
84 #define ARTWORKCOLOR(n) (IS_ARTWORKCLASS_CLASSICS(n) ? FC_RED : \
85 IS_ARTWORKCLASS_CONTRIBUTION(n) ? FC_YELLOW : \
86 IS_ARTWORKCLASS_LEVEL(n) ? FC_GREEN : \
87 IS_ARTWORKCLASS_USER(n) ? FC_RED : \
90 #define ARTWORKSORTING(n) (IS_ARTWORKCLASS_CLASSICS(n) ? 0 : \
91 IS_ARTWORKCLASS_CONTRIBUTION(n) ? 1 : \
92 IS_ARTWORKCLASS_LEVEL(n) ? 2 : \
93 IS_ARTWORKCLASS_USER(n) ? 3 : \
96 #define TOKEN_VALUE_POSITION 40
97 #define TOKEN_COMMENT_POSITION 60
99 #define MAX_COOKIE_LEN 256
101 #define ARTWORKINFO_FILENAME(type) ((type) == ARTWORK_TYPE_GRAPHICS ? \
102 GRAPHICSINFO_FILENAME : \
103 (type) == ARTWORK_TYPE_SOUNDS ? \
104 SOUNDSINFO_FILENAME : \
105 (type) == ARTWORK_TYPE_MUSIC ? \
106 MUSICINFO_FILENAME : "")
108 #define ARTWORK_DIRECTORY(type) ((type) == ARTWORK_TYPE_GRAPHICS ? \
109 GRAPHICS_DIRECTORY : \
110 (type) == ARTWORK_TYPE_SOUNDS ? \
112 (type) == ARTWORK_TYPE_MUSIC ? \
113 MUSIC_DIRECTORY : "")
115 #define OPTIONS_ARTWORK_DIRECTORY(type) ((type) == ARTWORK_TYPE_GRAPHICS ? \
116 options.graphics_directory : \
117 (type) == ARTWORK_TYPE_SOUNDS ? \
118 options.sounds_directory : \
119 (type) == ARTWORK_TYPE_MUSIC ? \
120 options.music_directory : "")
123 /* ------------------------------------------------------------------------- */
125 /* ------------------------------------------------------------------------- */
127 static char *getLevelClassDescription(TreeInfo *ldi)
129 int position = ldi->sort_priority / 100;
131 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
132 return levelclass_desc[position];
134 return "Unknown Level Class";
137 static char *getUserLevelDir(char *level_subdir)
139 static char *userlevel_dir = NULL;
140 char *data_dir = getUserDataDir();
141 char *userlevel_subdir = LEVELS_DIRECTORY;
146 if (level_subdir != NULL)
147 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
149 userlevel_dir = getPath2(data_dir, userlevel_subdir);
151 return userlevel_dir;
154 static char *getTapeDir(char *level_subdir)
156 static char *tape_dir = NULL;
157 char *data_dir = getUserDataDir();
158 char *tape_subdir = TAPES_DIRECTORY;
163 if (level_subdir != NULL)
164 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
166 tape_dir = getPath2(data_dir, tape_subdir);
171 static char *getScoreDir(char *level_subdir)
173 static char *score_dir = NULL;
174 char *data_dir = getCommonDataDir();
175 char *score_subdir = SCORES_DIRECTORY;
180 if (level_subdir != NULL)
181 score_dir = getPath3(data_dir, score_subdir, level_subdir);
183 score_dir = getPath2(data_dir, score_subdir);
188 static char *getLevelSetupDir(char *level_subdir)
190 static char *levelsetup_dir = NULL;
191 char *data_dir = getUserDataDir();
192 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
195 free(levelsetup_dir);
197 if (level_subdir != NULL)
198 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
200 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
202 return levelsetup_dir;
205 static char *getLevelDirFromTreeInfo(TreeInfo *node)
207 static char *level_dir = NULL;
210 return options.level_directory;
215 level_dir = getPath2((node->user_defined ? getUserLevelDir(NULL) :
216 options.level_directory), node->fullpath);
221 static char *getCurrentLevelDir()
223 return getLevelDirFromTreeInfo(leveldir_current);
226 static char *getDefaultGraphicsDir(char *graphics_subdir)
228 static char *graphics_dir = NULL;
230 if (graphics_subdir == NULL)
231 return options.graphics_directory;
236 graphics_dir = getPath2(options.graphics_directory, graphics_subdir);
241 static char *getDefaultSoundsDir(char *sounds_subdir)
243 static char *sounds_dir = NULL;
245 if (sounds_subdir == NULL)
246 return options.sounds_directory;
251 sounds_dir = getPath2(options.sounds_directory, sounds_subdir);
256 static char *getDefaultMusicDir(char *music_subdir)
258 static char *music_dir = NULL;
260 if (music_subdir == NULL)
261 return options.music_directory;
266 music_dir = getPath2(options.music_directory, music_subdir);
271 static char *getDefaultArtworkSet(int type)
273 return (type == TREE_TYPE_GRAPHICS_DIR ? GRAPHICS_SUBDIR :
274 type == TREE_TYPE_SOUNDS_DIR ? SOUNDS_SUBDIR :
275 type == TREE_TYPE_MUSIC_DIR ? MUSIC_SUBDIR : "");
278 static char *getDefaultArtworkDir(int type)
280 return (type == TREE_TYPE_GRAPHICS_DIR ?
281 getDefaultGraphicsDir(GRAPHICS_SUBDIR) :
282 type == TREE_TYPE_SOUNDS_DIR ?
283 getDefaultSoundsDir(SOUNDS_SUBDIR) :
284 type == TREE_TYPE_MUSIC_DIR ?
285 getDefaultMusicDir(MUSIC_SUBDIR) : "");
288 static char *getUserGraphicsDir()
290 static char *usergraphics_dir = NULL;
292 if (usergraphics_dir == NULL)
293 usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
295 return usergraphics_dir;
298 static char *getUserSoundsDir()
300 static char *usersounds_dir = NULL;
302 if (usersounds_dir == NULL)
303 usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
305 return usersounds_dir;
308 static char *getUserMusicDir()
310 static char *usermusic_dir = NULL;
312 if (usermusic_dir == NULL)
313 usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
315 return usermusic_dir;
318 static char *getSetupArtworkDir(TreeInfo *ti)
320 static char *artwork_dir = NULL;
322 if (artwork_dir != NULL)
325 artwork_dir = getPath2(ti->basepath, ti->fullpath);
330 void setLevelArtworkDir(TreeInfo *ti)
332 char **artwork_path_ptr, **artwork_set_ptr;
333 TreeInfo *level_artwork;
335 if (ti == NULL || leveldir_current == NULL)
338 artwork_path_ptr = &(LEVELDIR_ARTWORK_PATH(leveldir_current, ti->type));
339 artwork_set_ptr = &(LEVELDIR_ARTWORK_SET( leveldir_current, ti->type));
341 if (*artwork_path_ptr != NULL)
342 free(*artwork_path_ptr);
344 if ((level_artwork = getTreeInfoFromIdentifier(ti, *artwork_set_ptr)))
345 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
348 /* No (or non-existing) artwork configured in "levelinfo.conf". This would
349 normally result in using the artwork configured in the setup menu. But
350 if an artwork subdirectory exists (which might contain custom artwork
351 or an artwork configuration file), this level artwork must be treated
352 as relative to the default "classic" artwork, not to the artwork that
353 is currently configured in the setup menu. */
355 char *dir = getPath2(getCurrentLevelDir(), ARTWORK_DIRECTORY(ti->type));
357 if (*artwork_set_ptr != NULL)
358 free(*artwork_set_ptr);
362 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
363 *artwork_set_ptr = getStringCopy(getDefaultArtworkSet(ti->type));
367 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
368 *artwork_set_ptr = NULL;
375 inline static char *getLevelArtworkSet(int type)
377 if (leveldir_current == NULL)
380 return LEVELDIR_ARTWORK_SET(leveldir_current, type);
383 inline static char *getLevelArtworkDir(int type)
385 if (leveldir_current == NULL)
386 return UNDEFINED_FILENAME;
388 return LEVELDIR_ARTWORK_PATH(leveldir_current, type);
391 char *getLevelFilename(int nr)
393 static char *filename = NULL;
394 char basename[MAX_FILENAME_LEN];
396 if (filename != NULL)
400 sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
402 sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
404 filename = getPath2(getCurrentLevelDir(), basename);
409 char *getTapeFilename(int nr)
411 static char *filename = NULL;
412 char basename[MAX_FILENAME_LEN];
414 if (filename != NULL)
417 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
418 filename = getPath2(getTapeDir(leveldir_current->filename), basename);
423 char *getScoreFilename(int nr)
425 static char *filename = NULL;
426 char basename[MAX_FILENAME_LEN];
428 if (filename != NULL)
431 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
432 filename = getPath2(getScoreDir(leveldir_current->filename), basename);
437 char *getSetupFilename()
439 static char *filename = NULL;
441 if (filename != NULL)
444 filename = getPath2(getSetupDir(), SETUP_FILENAME);
449 static char *getCorrectedImageBasename(char *basename)
451 char *basename_corrected = basename;
453 #if defined(PLATFORM_MSDOS)
454 if (program.filename_prefix != NULL)
456 int prefix_len = strlen(program.filename_prefix);
458 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
459 basename_corrected = &basename[prefix_len];
461 /* if corrected filename is still longer than standard MS-DOS filename
462 size (8 characters + 1 dot + 3 characters file extension), shorten
463 filename by writing file extension after 8th basename character */
464 if (strlen(basename_corrected) > 8+1+3)
466 static char *msdos_filename = NULL;
468 if (msdos_filename != NULL)
469 free(msdos_filename);
471 msdos_filename = getStringCopy(basename_corrected);
472 strncpy(&msdos_filename[8], &basename[strlen(basename) - 1+3], 1+3 + 1);
477 return basename_corrected;
480 char *getCustomImageFilename(char *basename)
482 static char *filename = NULL;
483 boolean skip_setup_artwork = FALSE;
485 if (filename != NULL)
488 basename = getCorrectedImageBasename(basename);
490 if (!setup.override_level_graphics)
492 /* 1st try: look for special artwork in current level series directory */
493 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
494 if (fileExists(filename))
499 /* check if there is special artwork configured in level series config */
500 if (getLevelArtworkSet(ARTWORK_TYPE_GRAPHICS) != NULL)
502 /* 2nd try: look for special artwork configured in level series config */
503 filename = getPath2(getLevelArtworkDir(ARTWORK_TYPE_GRAPHICS), basename);
504 if (fileExists(filename))
509 /* take missing artwork configured in level set config from default */
510 skip_setup_artwork = TRUE;
514 if (!skip_setup_artwork)
516 /* 3rd try: look for special artwork in configured artwork directory */
517 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
518 if (fileExists(filename))
524 /* 4th try: look for default artwork in new default artwork directory */
525 filename = getPath2(getDefaultGraphicsDir(GRAPHICS_SUBDIR), basename);
526 if (fileExists(filename))
531 /* 5th try: look for default artwork in old default artwork directory */
532 filename = getPath2(options.graphics_directory, basename);
533 if (fileExists(filename))
536 return NULL; /* cannot find specified artwork file anywhere */
539 char *getCustomSoundFilename(char *basename)
541 static char *filename = NULL;
542 boolean skip_setup_artwork = FALSE;
544 if (filename != NULL)
547 if (!setup.override_level_sounds)
549 /* 1st try: look for special artwork in current level series directory */
550 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
551 if (fileExists(filename))
556 /* check if there is special artwork configured in level series config */
557 if (getLevelArtworkSet(ARTWORK_TYPE_SOUNDS) != NULL)
559 /* 2nd try: look for special artwork configured in level series config */
560 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
561 if (fileExists(filename))
566 /* take missing artwork configured in level set config from default */
567 skip_setup_artwork = TRUE;
571 if (!skip_setup_artwork)
573 /* 3rd try: look for special artwork in configured artwork directory */
574 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
575 if (fileExists(filename))
581 /* 4th try: look for default artwork in new default artwork directory */
582 filename = getPath2(getDefaultSoundsDir(SOUNDS_SUBDIR), basename);
583 if (fileExists(filename))
588 /* 5th try: look for default artwork in old default artwork directory */
589 filename = getPath2(options.sounds_directory, basename);
590 if (fileExists(filename))
593 return NULL; /* cannot find specified artwork file anywhere */
596 char *getCustomArtworkFilename(char *basename, int type)
598 if (type == ARTWORK_TYPE_GRAPHICS)
599 return getCustomImageFilename(basename);
600 else if (type == ARTWORK_TYPE_SOUNDS)
601 return getCustomSoundFilename(basename);
603 return UNDEFINED_FILENAME;
606 char *getCustomArtworkConfigFilename(int type)
608 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
611 char *getCustomArtworkLevelConfigFilename(int type)
613 static char *filename = NULL;
615 if (filename != NULL)
618 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
623 char *getCustomMusicDirectory(void)
625 static char *directory = NULL;
626 boolean skip_setup_artwork = FALSE;
628 if (directory != NULL)
631 if (!setup.override_level_music)
633 /* 1st try: look for special artwork in current level series directory */
634 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
635 if (fileExists(directory))
640 /* check if there is special artwork configured in level series config */
641 if (getLevelArtworkSet(ARTWORK_TYPE_MUSIC) != NULL)
643 /* 2nd try: look for special artwork configured in level series config */
644 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
645 if (fileExists(directory))
650 /* take missing artwork configured in level set config from default */
651 skip_setup_artwork = TRUE;
655 if (!skip_setup_artwork)
657 /* 3rd try: look for special artwork in configured artwork directory */
658 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
659 if (fileExists(directory))
665 /* 4th try: look for default artwork in new default artwork directory */
666 directory = getStringCopy(getDefaultMusicDir(MUSIC_SUBDIR));
667 if (fileExists(directory))
672 /* 5th try: look for default artwork in old default artwork directory */
673 directory = getStringCopy(options.music_directory);
674 if (fileExists(directory))
677 return NULL; /* cannot find specified artwork file anywhere */
680 void InitTapeDirectory(char *level_subdir)
682 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
683 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
684 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
687 void InitScoreDirectory(char *level_subdir)
689 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
690 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
691 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
694 static void SaveUserLevelInfo();
696 void InitUserLevelDirectory(char *level_subdir)
698 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
700 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
701 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
702 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
708 void InitLevelSetupDirectory(char *level_subdir)
710 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
711 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
712 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
716 /* ------------------------------------------------------------------------- */
717 /* some functions to handle lists of level directories */
718 /* ------------------------------------------------------------------------- */
720 TreeInfo *newTreeInfo()
722 return checked_calloc(sizeof(TreeInfo));
725 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
727 node_new->next = *node_first;
728 *node_first = node_new;
731 int numTreeInfo(TreeInfo *node)
744 boolean validLevelSeries(TreeInfo *node)
746 return (node != NULL && !node->node_group && !node->parent_link);
749 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
754 if (node->node_group) /* enter level group (step down into tree) */
755 return getFirstValidTreeInfoEntry(node->node_group);
756 else if (node->parent_link) /* skip start entry of level group */
758 if (node->next) /* get first real level series entry */
759 return getFirstValidTreeInfoEntry(node->next);
760 else /* leave empty level group and go on */
761 return getFirstValidTreeInfoEntry(node->node_parent->next);
763 else /* this seems to be a regular level series */
767 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
772 if (node->node_parent == NULL) /* top level group */
773 return *node->node_top;
774 else /* sub level group */
775 return node->node_parent->node_group;
778 int numTreeInfoInGroup(TreeInfo *node)
780 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
783 int posTreeInfo(TreeInfo *node)
785 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
790 if (node_cmp == node)
794 node_cmp = node_cmp->next;
800 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
802 TreeInfo *node_default = node;
817 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
819 if (identifier == NULL)
824 if (node->node_group)
826 TreeInfo *node_group;
828 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
833 else if (!node->parent_link)
835 if (strcmp(identifier, node->identifier) == 0)
845 void dumpTreeInfo(TreeInfo *node, int depth)
849 printf("Dumping TreeInfo:\n");
853 for (i=0; i<(depth + 1) * 3; i++)
856 printf("filename == '%s' (%s) [%s] (%d)\n",
857 node->filename, node->name, node->identifier, node->sort_priority);
859 if (node->node_group != NULL)
860 dumpTreeInfo(node->node_group, depth + 1);
866 void sortTreeInfo(TreeInfo **node_first,
867 int (*compare_function)(const void *, const void *))
869 int num_nodes = numTreeInfo(*node_first);
870 TreeInfo **sort_array;
871 TreeInfo *node = *node_first;
877 /* allocate array for sorting structure pointers */
878 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
880 /* writing structure pointers to sorting array */
881 while (i < num_nodes && node) /* double boundary check... */
883 sort_array[i] = node;
889 /* sorting the structure pointers in the sorting array */
890 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
893 /* update the linkage of list elements with the sorted node array */
894 for (i=0; i<num_nodes - 1; i++)
895 sort_array[i]->next = sort_array[i + 1];
896 sort_array[num_nodes - 1]->next = NULL;
898 /* update the linkage of the main list anchor pointer */
899 *node_first = sort_array[0];
903 /* now recursively sort the level group structures */
907 if (node->node_group != NULL)
908 sortTreeInfo(&node->node_group, compare_function);
915 /* ========================================================================= */
916 /* some stuff from "files.c" */
917 /* ========================================================================= */
919 #if defined(PLATFORM_WIN32)
921 #define S_IRGRP S_IRUSR
924 #define S_IROTH S_IRUSR
927 #define S_IWGRP S_IWUSR
930 #define S_IWOTH S_IWUSR
933 #define S_IXGRP S_IXUSR
936 #define S_IXOTH S_IXUSR
939 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
944 #endif /* PLATFORM_WIN32 */
946 /* file permissions for newly written files */
947 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
948 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
949 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
951 #define MODE_W_PRIVATE (S_IWUSR)
952 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
953 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
955 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
956 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
958 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
959 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
961 char *getUserDataDir(void)
963 static char *userdata_dir = NULL;
965 if (userdata_dir == NULL)
966 userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
971 char *getCommonDataDir(void)
973 static char *common_data_dir = NULL;
975 #if defined(PLATFORM_WIN32)
976 if (common_data_dir == NULL)
978 char *dir = checked_malloc(MAX_PATH + 1);
980 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
981 && strcmp(dir, "") != 0) /* empty for Windows 95/98 */
982 common_data_dir = getPath2(dir, program.userdata_directory);
984 common_data_dir = options.rw_base_directory;
987 if (common_data_dir == NULL)
988 common_data_dir = options.rw_base_directory;
991 return common_data_dir;
996 return getUserDataDir();
999 static mode_t posix_umask(mode_t mask)
1001 #if defined(PLATFORM_UNIX)
1008 static int posix_mkdir(const char *pathname, mode_t mode)
1010 #if defined(PLATFORM_WIN32)
1011 return mkdir(pathname);
1013 return mkdir(pathname, mode);
1017 void createDirectory(char *dir, char *text, int permission_class)
1019 /* leave "other" permissions in umask untouched, but ensure group parts
1020 of USERDATA_DIR_MODE are not masked */
1021 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1022 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1023 mode_t normal_umask = posix_umask(0);
1024 mode_t group_umask = ~(dir_mode & S_IRWXG);
1025 posix_umask(normal_umask & group_umask);
1027 if (access(dir, F_OK) != 0)
1028 if (posix_mkdir(dir, dir_mode) != 0)
1029 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1031 posix_umask(normal_umask); /* reset normal umask */
1034 void InitUserDataDirectory()
1036 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
1039 void SetFilePermissions(char *filename, int permission_class)
1041 chmod(filename, (permission_class == PERMS_PRIVATE ?
1042 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1045 char *getCookie(char *file_type)
1047 static char cookie[MAX_COOKIE_LEN + 1];
1049 if (strlen(program.cookie_prefix) + 1 +
1050 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1051 return "[COOKIE ERROR]"; /* should never happen */
1053 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1054 program.cookie_prefix, file_type,
1055 program.version_major, program.version_minor);
1060 int getFileVersionFromCookieString(const char *cookie)
1062 const char *ptr_cookie1, *ptr_cookie2;
1063 const char *pattern1 = "_FILE_VERSION_";
1064 const char *pattern2 = "?.?";
1065 const int len_cookie = strlen(cookie);
1066 const int len_pattern1 = strlen(pattern1);
1067 const int len_pattern2 = strlen(pattern2);
1068 const int len_pattern = len_pattern1 + len_pattern2;
1069 int version_major, version_minor;
1071 if (len_cookie <= len_pattern)
1074 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1075 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1077 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1080 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1081 ptr_cookie2[1] != '.' ||
1082 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1085 version_major = ptr_cookie2[0] - '0';
1086 version_minor = ptr_cookie2[2] - '0';
1088 return VERSION_IDENT(version_major, version_minor, 0);
1091 boolean checkCookieString(const char *cookie, const char *template)
1093 const char *pattern = "_FILE_VERSION_?.?";
1094 const int len_cookie = strlen(cookie);
1095 const int len_template = strlen(template);
1096 const int len_pattern = strlen(pattern);
1098 if (len_cookie != len_template)
1101 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1107 /* ------------------------------------------------------------------------- */
1108 /* setup file list and hash handling functions */
1109 /* ------------------------------------------------------------------------- */
1111 char *getFormattedSetupEntry(char *token, char *value)
1114 static char entry[MAX_LINE_LEN];
1116 /* start with the token and some spaces to format output line */
1117 sprintf(entry, "%s:", token);
1118 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1121 /* continue with the token's value */
1122 strcat(entry, value);
1127 SetupFileList *newSetupFileList(char *token, char *value)
1129 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1131 new->token = getStringCopy(token);
1132 new->value = getStringCopy(value);
1139 void freeSetupFileList(SetupFileList *list)
1149 freeSetupFileList(list->next);
1153 char *getListEntry(SetupFileList *list, char *token)
1158 if (strcmp(list->token, token) == 0)
1161 return getListEntry(list->next, token);
1164 void setListEntry(SetupFileList *list, char *token, char *value)
1169 if (strcmp(list->token, token) == 0)
1174 list->value = getStringCopy(value);
1176 else if (list->next == NULL)
1177 list->next = newSetupFileList(token, value);
1179 setListEntry(list->next, token, value);
1183 static void printSetupFileList(SetupFileList *list)
1188 printf("token: '%s'\n", list->token);
1189 printf("value: '%s'\n", list->value);
1191 printSetupFileList(list->next);
1196 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1197 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1198 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1199 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1201 #define insert_hash_entry hashtable_insert
1202 #define search_hash_entry hashtable_search
1203 #define change_hash_entry hashtable_change
1204 #define remove_hash_entry hashtable_remove
1207 static unsigned int get_hash_from_key(void *key)
1212 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1213 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1214 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1215 it works better than many other constants, prime or not) has never been
1216 adequately explained.
1218 If you just want to have a good hash function, and cannot wait, djb2
1219 is one of the best string hash functions i know. It has excellent
1220 distribution and speed on many different sets of keys and table sizes.
1221 You are not likely to do better with one of the "well known" functions
1222 such as PJW, K&R, etc.
1224 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1227 char *str = (char *)key;
1228 unsigned int hash = 5381;
1231 while ((c = *str++))
1232 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1237 static int keys_are_equal(void *key1, void *key2)
1239 return (strcmp((char *)key1, (char *)key2) == 0);
1242 SetupFileHash *newSetupFileHash()
1244 SetupFileHash *new_hash =
1245 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1250 void freeSetupFileHash(SetupFileHash *hash)
1255 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1258 char *getHashEntry(SetupFileHash *hash, char *token)
1263 return search_hash_entry(hash, token);
1266 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1273 value_copy = getStringCopy(value);
1275 /* change value; if it does not exist, insert it as new */
1276 if (!change_hash_entry(hash, token, value_copy))
1277 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1278 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1283 static void printSetupFileHash(SetupFileHash *hash)
1285 BEGIN_HASH_ITERATION(hash, itr)
1287 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1288 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1290 END_HASH_ITERATION(hash, itr)
1295 static void *loadSetupFileData(char *filename, boolean use_hash)
1298 char line[MAX_LINE_LEN];
1299 char *token, *value, *line_ptr;
1300 void *setup_file_data;
1304 setup_file_data = newSetupFileHash();
1306 setup_file_data = newSetupFileList("", "");
1308 if (!(file = fopen(filename, MODE_READ)))
1310 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1316 /* read next line of input file */
1317 if (!fgets(line, MAX_LINE_LEN, file))
1320 /* cut trailing comment or whitespace from input line */
1321 for (line_ptr = line; *line_ptr; line_ptr++)
1323 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1330 /* cut trailing whitespaces from input line */
1331 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1332 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1335 /* ignore empty lines */
1339 line_len = strlen(line);
1341 /* cut leading whitespaces from token */
1342 for (token = line; *token; token++)
1343 if (*token != ' ' && *token != '\t')
1346 /* find end of token */
1347 for (line_ptr = token; *line_ptr; line_ptr++)
1349 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1356 if (line_ptr < line + line_len)
1357 value = line_ptr + 1;
1361 /* cut leading whitespaces from value */
1362 for (; *value; value++)
1363 if (*value != ' ' && *value != '\t')
1366 if (*token && *value)
1369 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1371 setListEntry((SetupFileList *)setup_file_data, token, value);
1379 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1380 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1384 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1385 SetupFileList *first_valid_list_entry = setup_file_list->next;
1387 /* free empty list header */
1388 setup_file_list->next = NULL;
1389 freeSetupFileList(setup_file_list);
1390 setup_file_data = first_valid_list_entry;
1392 if (first_valid_list_entry == NULL)
1393 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1396 return setup_file_data;
1399 SetupFileList *loadSetupFileList(char *filename)
1401 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1404 SetupFileHash *loadSetupFileHash(char *filename)
1406 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1409 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1412 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1415 Error(ERR_WARN, "configuration file has no file identifier");
1416 else if (!checkCookieString(value, identifier))
1417 Error(ERR_WARN, "configuration file has wrong file identifier");
1421 /* ========================================================================= */
1422 /* setup file stuff */
1423 /* ========================================================================= */
1425 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1426 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1427 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1429 /* level directory info */
1430 #define LEVELINFO_TOKEN_IDENTIFIER 0
1431 #define LEVELINFO_TOKEN_NAME 1
1432 #define LEVELINFO_TOKEN_NAME_SORTING 2
1433 #define LEVELINFO_TOKEN_AUTHOR 3
1434 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1435 #define LEVELINFO_TOKEN_LEVELS 5
1436 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
1437 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
1438 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
1439 #define LEVELINFO_TOKEN_READONLY 9
1440 #define LEVELINFO_TOKEN_GRAPHICS_SET 10
1441 #define LEVELINFO_TOKEN_SOUNDS_SET 11
1442 #define LEVELINFO_TOKEN_MUSIC_SET 12
1444 #define NUM_LEVELINFO_TOKENS 13
1446 static LevelDirTree ldi;
1448 static struct TokenInfo levelinfo_tokens[] =
1450 /* level directory info */
1451 { TYPE_STRING, &ldi.identifier, "identifier" },
1452 { TYPE_STRING, &ldi.name, "name" },
1453 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1454 { TYPE_STRING, &ldi.author, "author" },
1455 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1456 { TYPE_INTEGER, &ldi.levels, "levels" },
1457 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1458 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1459 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1460 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1461 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1462 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1463 { TYPE_STRING, &ldi.music_set, "music_set" }
1466 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1470 ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1471 ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1472 ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1473 ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1476 ldi->node_parent = NULL;
1477 ldi->node_group = NULL;
1481 ldi->cl_cursor = -1;
1483 ldi->filename = NULL;
1484 ldi->fullpath = NULL;
1485 ldi->basepath = NULL;
1486 ldi->identifier = NULL;
1487 ldi->name = getStringCopy(ANONYMOUS_NAME);
1488 ldi->name_sorting = NULL;
1489 ldi->author = getStringCopy(ANONYMOUS_NAME);
1491 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1492 ldi->parent_link = FALSE;
1493 ldi->user_defined = FALSE;
1495 ldi->class_desc = NULL;
1497 if (ldi->type == TREE_TYPE_LEVEL_DIR)
1499 ldi->imported_from = NULL;
1501 ldi->graphics_set = NULL;
1502 ldi->sounds_set = NULL;
1503 ldi->music_set = NULL;
1504 ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1505 ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1506 ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1509 ldi->first_level = 0;
1510 ldi->last_level = 0;
1511 ldi->level_group = FALSE;
1512 ldi->handicap_level = 0;
1513 ldi->readonly = TRUE;
1517 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1521 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1523 setTreeInfoToDefaults(ldi, TREE_TYPE_UNDEFINED);
1529 /* copy all values from the parent structure */
1531 ldi->type = parent->type;
1533 ldi->node_top = parent->node_top;
1534 ldi->node_parent = parent;
1535 ldi->node_group = NULL;
1539 ldi->cl_cursor = -1;
1541 ldi->filename = NULL;
1542 ldi->fullpath = NULL;
1543 ldi->basepath = NULL;
1544 ldi->identifier = NULL;
1545 ldi->name = getStringCopy(ANONYMOUS_NAME);
1546 ldi->name_sorting = NULL;
1547 ldi->author = getStringCopy(parent->author);
1549 ldi->sort_priority = parent->sort_priority;
1550 ldi->parent_link = FALSE;
1551 ldi->user_defined = parent->user_defined;
1552 ldi->color = parent->color;
1553 ldi->class_desc = getStringCopy(parent->class_desc);
1555 if (ldi->type == TREE_TYPE_LEVEL_DIR)
1557 ldi->imported_from = getStringCopy(parent->imported_from);
1559 ldi->graphics_set = NULL;
1560 ldi->sounds_set = NULL;
1561 ldi->music_set = NULL;
1562 ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1563 ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1564 ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1567 ldi->first_level = 0;
1568 ldi->last_level = 0;
1569 ldi->level_group = FALSE;
1570 ldi->handicap_level = 0;
1571 ldi->readonly = TRUE;
1577 /* first copy all values from the parent structure ... */
1580 /* ... then set all fields to default that cannot be inherited from parent.
1581 This is especially important for all those fields that can be set from
1582 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1583 calls 'free()' for all already set token values which requires that no
1584 other structure's pointer may point to them!
1587 ldi->filename = NULL;
1588 ldi->fullpath = NULL;
1589 ldi->basepath = NULL;
1590 ldi->identifier = NULL;
1591 ldi->name = getStringCopy(ANONYMOUS_NAME);
1592 ldi->name_sorting = NULL;
1593 ldi->author = getStringCopy(parent->author);
1595 ldi->imported_from = getStringCopy(parent->imported_from);
1596 ldi->class_desc = getStringCopy(parent->class_desc);
1598 ldi->graphics_set = NULL;
1599 ldi->sounds_set = NULL;
1600 ldi->music_set = NULL;
1601 ldi->graphics_path = NULL;
1602 ldi->sounds_path = NULL;
1603 ldi->music_path = NULL;
1605 ldi->level_group = FALSE;
1606 ldi->parent_link = FALSE;
1608 ldi->node_top = parent->node_top;
1609 ldi->node_parent = parent;
1610 ldi->node_group = NULL;
1616 void setSetupInfo(struct TokenInfo *token_info,
1617 int token_nr, char *token_value)
1619 int token_type = token_info[token_nr].type;
1620 void *setup_value = token_info[token_nr].value;
1622 if (token_value == NULL)
1625 /* set setup field to corresponding token value */
1630 *(boolean *)setup_value = get_boolean_from_string(token_value);
1634 *(Key *)setup_value = getKeyFromKeyName(token_value);
1638 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1642 *(int *)setup_value = get_integer_from_string(token_value);
1646 if (*(char **)setup_value != NULL)
1647 free(*(char **)setup_value);
1648 *(char **)setup_value = getStringCopy(token_value);
1656 static int compareTreeInfoEntries(const void *object1, const void *object2)
1658 const TreeInfo *entry1 = *((TreeInfo **)object1);
1659 const TreeInfo *entry2 = *((TreeInfo **)object2);
1660 int class_sorting1, class_sorting2;
1663 if (entry1->type == TREE_TYPE_LEVEL_DIR)
1665 class_sorting1 = LEVELSORTING(entry1);
1666 class_sorting2 = LEVELSORTING(entry2);
1670 class_sorting1 = ARTWORKSORTING(entry1);
1671 class_sorting2 = ARTWORKSORTING(entry2);
1674 if (entry1->parent_link || entry2->parent_link)
1675 compare_result = (entry1->parent_link ? -1 : +1);
1676 else if (entry1->sort_priority == entry2->sort_priority)
1678 char *name1 = getStringToLower(entry1->name_sorting);
1679 char *name2 = getStringToLower(entry2->name_sorting);
1681 compare_result = strcmp(name1, name2);
1686 else if (class_sorting1 == class_sorting2)
1687 compare_result = entry1->sort_priority - entry2->sort_priority;
1689 compare_result = class_sorting1 - class_sorting2;
1691 return compare_result;
1694 static void createParentTreeInfoNode(TreeInfo *node_parent)
1698 if (node_parent == NULL)
1701 ti_new = newTreeInfo();
1702 setTreeInfoToDefaults(ti_new, node_parent->type);
1704 ti_new->node_parent = node_parent;
1705 ti_new->parent_link = TRUE;
1707 ti_new->identifier = getStringCopy(node_parent->identifier);
1708 ti_new->name = ".. (parent directory)";
1709 ti_new->name_sorting = getStringCopy(ti_new->name);
1711 ti_new->filename = "..";
1712 ti_new->fullpath = getStringCopy(node_parent->fullpath);
1714 ti_new->sort_priority = node_parent->sort_priority;
1715 ti_new->class_desc = getLevelClassDescription(ti_new);
1717 pushTreeInfo(&node_parent->node_group, ti_new);
1720 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1721 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1723 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1724 TreeInfo *node_parent,
1725 char *level_directory,
1726 char *directory_name)
1728 char *directory_path = getPath2(level_directory, directory_name);
1729 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1730 SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1731 LevelDirTree *leveldir_new = NULL;
1734 if (setup_file_hash == NULL)
1736 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1738 free(directory_path);
1744 leveldir_new = newTreeInfo();
1747 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1749 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1751 leveldir_new->filename = getStringCopy(directory_name);
1753 checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1755 /* set all structure fields according to the token/value pairs */
1756 ldi = *leveldir_new;
1757 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1758 setSetupInfo(levelinfo_tokens, i,
1759 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1760 *leveldir_new = ldi;
1762 if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1764 free(leveldir_new->name);
1765 leveldir_new->name = getStringCopy(leveldir_new->filename);
1768 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1770 if (leveldir_new->identifier == NULL)
1771 leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1773 if (leveldir_new->name_sorting == NULL)
1774 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1776 if (node_parent == NULL) /* top level group */
1778 leveldir_new->basepath = level_directory;
1779 leveldir_new->fullpath = leveldir_new->filename;
1781 else /* sub level group */
1783 leveldir_new->basepath = node_parent->basepath;
1784 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1787 if (leveldir_new->levels < 1)
1788 leveldir_new->levels = 1;
1790 leveldir_new->last_level =
1791 leveldir_new->first_level + leveldir_new->levels - 1;
1793 leveldir_new->user_defined =
1794 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1796 leveldir_new->color = LEVELCOLOR(leveldir_new);
1797 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1799 leveldir_new->handicap_level = /* set handicap to default value */
1800 (leveldir_new->user_defined ?
1801 leveldir_new->last_level :
1802 leveldir_new->first_level);
1804 pushTreeInfo(node_first, leveldir_new);
1806 freeSetupFileHash(setup_file_hash);
1808 if (leveldir_new->level_group)
1810 /* create node to link back to current level directory */
1811 createParentTreeInfoNode(leveldir_new);
1813 /* step into sub-directory and look for more level series */
1814 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1815 leveldir_new, directory_path);
1818 free(directory_path);
1824 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1825 TreeInfo *node_parent,
1826 char *level_directory)
1829 struct dirent *dir_entry;
1830 boolean valid_entry_found = FALSE;
1832 if ((dir = opendir(level_directory)) == NULL)
1834 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1838 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1840 struct stat file_status;
1841 char *directory_name = dir_entry->d_name;
1842 char *directory_path = getPath2(level_directory, directory_name);
1844 /* skip entries for current and parent directory */
1845 if (strcmp(directory_name, ".") == 0 ||
1846 strcmp(directory_name, "..") == 0)
1848 free(directory_path);
1852 /* find out if directory entry is itself a directory */
1853 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1854 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1856 free(directory_path);
1860 free(directory_path);
1862 if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1863 strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1864 strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1867 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1874 if (!valid_entry_found)
1876 /* check if this directory directly contains a file "levelinfo.conf" */
1877 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1878 level_directory, ".");
1881 if (!valid_entry_found)
1882 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1886 void LoadLevelInfo()
1888 InitUserLevelDirectory(getLoginName());
1890 DrawInitText("Loading level series:", 120, FC_GREEN);
1892 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1893 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1895 /* before sorting, the first entries will be from the user directory */
1896 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1898 if (leveldir_first == NULL)
1899 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1901 sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1904 dumpTreeInfo(leveldir_first, 0);
1908 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1909 TreeInfo *node_parent,
1910 char *base_directory,
1911 char *directory_name, int type)
1913 char *directory_path = getPath2(base_directory, directory_name);
1914 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1915 SetupFileHash *setup_file_hash = NULL;
1916 TreeInfo *artwork_new = NULL;
1919 if (access(filename, F_OK) == 0) /* file exists */
1920 setup_file_hash = loadSetupFileHash(filename);
1922 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
1925 struct dirent *dir_entry;
1926 boolean valid_file_found = FALSE;
1928 if ((dir = opendir(directory_path)) != NULL)
1930 while ((dir_entry = readdir(dir)) != NULL)
1932 char *entry_name = dir_entry->d_name;
1934 if (FileIsArtworkType(entry_name, type))
1936 valid_file_found = TRUE;
1944 if (!valid_file_found)
1946 if (strcmp(directory_name, ".") != 0)
1947 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1949 free(directory_path);
1956 artwork_new = newTreeInfo();
1959 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1961 setTreeInfoToDefaults(artwork_new, type);
1963 artwork_new->filename = getStringCopy(directory_name);
1965 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
1968 checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
1971 /* set all structure fields according to the token/value pairs */
1973 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1974 setSetupInfo(levelinfo_tokens, i,
1975 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1978 if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1980 free(artwork_new->name);
1981 artwork_new->name = getStringCopy(artwork_new->filename);
1985 DrawInitText(artwork_new->name, 150, FC_YELLOW);
1988 if (artwork_new->identifier == NULL)
1989 artwork_new->identifier = getStringCopy(artwork_new->filename);
1991 if (artwork_new->name_sorting == NULL)
1992 artwork_new->name_sorting = getStringCopy(artwork_new->name);
1995 if (node_parent == NULL) /* top level group */
1997 artwork_new->basepath = getStringCopy(base_directory);
1998 artwork_new->fullpath = getStringCopy(artwork_new->filename);
2000 else /* sub level group */
2002 artwork_new->basepath = getStringCopy(node_parent->basepath);
2003 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
2006 artwork_new->user_defined =
2007 (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
2009 /* (may use ".sort_priority" from "setup_file_hash" above) */
2010 artwork_new->color = ARTWORKCOLOR(artwork_new);
2011 artwork_new->class_desc = getLevelClassDescription(artwork_new);
2013 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
2015 if (artwork_new->name != NULL)
2016 free(artwork_new->name);
2018 if (strcmp(artwork_new->filename, ".") == 0)
2020 if (artwork_new->user_defined)
2022 artwork_new->identifier = getStringCopy("private");
2023 artwork_new->sort_priority = ARTWORKCLASS_USER;
2027 artwork_new->identifier = getStringCopy("classic");
2028 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
2031 /* set to new values after changing ".sort_priority" */
2032 artwork_new->color = ARTWORKCOLOR(artwork_new);
2033 artwork_new->class_desc = getLevelClassDescription(artwork_new);
2037 artwork_new->identifier = getStringCopy(artwork_new->filename);
2040 artwork_new->name = getStringCopy(artwork_new->identifier);
2041 artwork_new->name_sorting = getStringCopy(artwork_new->name);
2044 DrawInitText(artwork_new->name, 150, FC_YELLOW);
2046 pushTreeInfo(node_first, artwork_new);
2048 freeSetupFileHash(setup_file_hash);
2050 free(directory_path);
2056 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2057 TreeInfo *node_parent,
2058 char *base_directory, int type)
2061 struct dirent *dir_entry;
2062 boolean valid_entry_found = FALSE;
2064 if ((dir = opendir(base_directory)) == NULL)
2066 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2067 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2071 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2073 struct stat file_status;
2074 char *directory_name = dir_entry->d_name;
2075 char *directory_path = getPath2(base_directory, directory_name);
2077 /* skip entries for current and parent directory */
2078 if (strcmp(directory_name, ".") == 0 ||
2079 strcmp(directory_name, "..") == 0)
2081 free(directory_path);
2085 /* find out if directory entry is itself a directory */
2086 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2087 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2089 free(directory_path);
2093 free(directory_path);
2095 /* check if this directory contains artwork with or without config file */
2096 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2098 directory_name, type);
2103 /* check if this directory directly contains artwork itself */
2104 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2105 base_directory, ".",
2107 if (!valid_entry_found)
2108 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2112 static TreeInfo *getDummyArtworkInfo(int type)
2114 /* this is only needed when there is completely no artwork available */
2115 TreeInfo *artwork_new = newTreeInfo();
2117 setTreeInfoToDefaults(artwork_new, type);
2119 artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2120 artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2121 artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2123 if (artwork_new->name != NULL)
2124 free(artwork_new->name);
2126 artwork_new->identifier = getStringCopy(UNDEFINED_FILENAME);
2127 artwork_new->name = getStringCopy(UNDEFINED_FILENAME);
2128 artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2133 void LoadArtworkInfo()
2135 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2137 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2138 options.graphics_directory,
2139 TREE_TYPE_GRAPHICS_DIR);
2140 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2141 getUserGraphicsDir(),
2142 TREE_TYPE_GRAPHICS_DIR);
2144 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2145 options.sounds_directory,
2146 TREE_TYPE_SOUNDS_DIR);
2147 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2149 TREE_TYPE_SOUNDS_DIR);
2151 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2152 options.music_directory,
2153 TREE_TYPE_MUSIC_DIR);
2154 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2156 TREE_TYPE_MUSIC_DIR);
2158 if (artwork.gfx_first == NULL)
2159 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2160 if (artwork.snd_first == NULL)
2161 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2162 if (artwork.mus_first == NULL)
2163 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2165 /* before sorting, the first entries will be from the user directory */
2166 artwork.gfx_current =
2167 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2168 if (artwork.gfx_current == NULL)
2169 artwork.gfx_current =
2170 getTreeInfoFromIdentifier(artwork.gfx_first, GRAPHICS_SUBDIR);
2171 if (artwork.gfx_current == NULL)
2172 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2174 artwork.snd_current =
2175 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2176 if (artwork.snd_current == NULL)
2177 artwork.snd_current =
2178 getTreeInfoFromIdentifier(artwork.snd_first, SOUNDS_SUBDIR);
2179 if (artwork.snd_current == NULL)
2180 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2182 artwork.mus_current =
2183 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2184 if (artwork.mus_current == NULL)
2185 artwork.mus_current =
2186 getTreeInfoFromIdentifier(artwork.mus_first, MUSIC_SUBDIR);
2187 if (artwork.mus_current == NULL)
2188 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2190 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2191 artwork.snd_current_identifier = artwork.snd_current->identifier;
2192 artwork.mus_current_identifier = artwork.mus_current->identifier;
2195 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2196 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2197 printf("music set == %s\n\n", artwork.mus_current_identifier);
2200 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2201 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2202 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2205 dumpTreeInfo(artwork.gfx_first, 0);
2206 dumpTreeInfo(artwork.snd_first, 0);
2207 dumpTreeInfo(artwork.mus_first, 0);
2211 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2212 LevelDirTree *level_node)
2214 /* recursively check all level directories for artwork sub-directories */
2218 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2219 ARTWORK_DIRECTORY((*artwork_node)->type));
2222 if (!level_node->parent_link)
2223 printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2224 level_node->filename, level_node->name);
2227 if (!level_node->parent_link)
2229 TreeInfo *topnode_last = *artwork_node;
2231 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2232 (*artwork_node)->type);
2234 if (topnode_last != *artwork_node)
2236 free((*artwork_node)->identifier);
2237 free((*artwork_node)->name);
2238 free((*artwork_node)->name_sorting);
2240 (*artwork_node)->identifier = getStringCopy(level_node->filename);
2241 (*artwork_node)->name = getStringCopy(level_node->name);
2242 (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2244 (*artwork_node)->sort_priority = level_node->sort_priority;
2245 (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2251 if (level_node->node_group != NULL)
2252 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2254 level_node = level_node->next;
2258 void LoadLevelArtworkInfo()
2260 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2262 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2263 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2264 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2266 /* needed for reloading level artwork not known at ealier stage */
2267 if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2269 artwork.gfx_current =
2270 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2271 if (artwork.gfx_current == NULL)
2272 artwork.gfx_current =
2273 getTreeInfoFromIdentifier(artwork.gfx_first, GRAPHICS_SUBDIR);
2274 if (artwork.gfx_current == NULL)
2275 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2278 if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2280 artwork.snd_current =
2281 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2282 if (artwork.snd_current == NULL)
2283 artwork.snd_current =
2284 getTreeInfoFromIdentifier(artwork.snd_first, SOUNDS_SUBDIR);
2285 if (artwork.snd_current == NULL)
2286 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2289 if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2291 artwork.mus_current =
2292 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2293 if (artwork.mus_current == NULL)
2294 artwork.mus_current =
2295 getTreeInfoFromIdentifier(artwork.mus_first, MUSIC_SUBDIR);
2296 if (artwork.mus_current == NULL)
2297 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2300 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2301 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2302 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2305 dumpTreeInfo(artwork.gfx_first, 0);
2306 dumpTreeInfo(artwork.snd_first, 0);
2307 dumpTreeInfo(artwork.mus_first, 0);
2311 static void SaveUserLevelInfo()
2317 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2319 if (!(file = fopen(filename, MODE_WRITE)))
2321 Error(ERR_WARN, "cannot write level info file '%s'", filename);
2326 /* always start with reliable default values */
2327 setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2330 /* !!! FIX ME !!! */
2331 setString(&ldi.name, getLoginName());
2332 setString(&ldi.author, getRealName());
2334 ldi.first_level = 1;
2335 ldi.sort_priority = LEVELCLASS_USER_START;
2336 ldi.readonly = FALSE;
2337 setString(&ldi.graphics_set, GRAPHICS_SUBDIR);
2338 setString(&ldi.sounds_set, SOUNDS_SUBDIR);
2339 setString(&ldi.music_set, MUSIC_SUBDIR);
2341 ldi.name = getStringCopy(getLoginName());
2342 ldi.author = getStringCopy(getRealName());
2344 ldi.first_level = 1;
2345 ldi.sort_priority = LEVELCLASS_USER_START;
2346 ldi.readonly = FALSE;
2347 ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2348 ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2349 ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2352 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2353 getCookie("LEVELINFO")));
2355 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2356 if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2357 i != LEVELINFO_TOKEN_NAME_SORTING &&
2358 i != LEVELINFO_TOKEN_IMPORTED_FROM)
2359 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2364 SetFilePermissions(filename, PERMS_PRIVATE);
2367 char *getSetupValue(int type, void *value)
2369 static char value_string[MAX_LINE_LEN];
2377 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2381 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2385 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2389 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2393 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2397 sprintf(value_string, "%d", *(int *)value);
2401 strcpy(value_string, *(char **)value);
2405 value_string[0] = '\0';
2409 return value_string;
2412 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2416 static char token_string[MAX_LINE_LEN];
2417 int token_type = token_info[token_nr].type;
2418 void *setup_value = token_info[token_nr].value;
2419 char *token_text = token_info[token_nr].text;
2420 char *value_string = getSetupValue(token_type, setup_value);
2422 /* build complete token string */
2423 sprintf(token_string, "%s%s", prefix, token_text);
2425 /* build setup entry line */
2426 line = getFormattedSetupEntry(token_string, value_string);
2428 if (token_type == TYPE_KEY_X11)
2430 Key key = *(Key *)setup_value;
2431 char *keyname = getKeyNameFromKey(key);
2433 /* add comment, if useful */
2434 if (strcmp(keyname, "(undefined)") != 0 &&
2435 strcmp(keyname, "(unknown)") != 0)
2437 /* add at least one whitespace */
2439 for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2443 strcat(line, keyname);
2450 void LoadLevelSetup_LastSeries()
2453 SetupFileHash *level_setup_hash = NULL;
2455 /* always start with reliable default values */
2456 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2458 /* ----------------------------------------------------------------------- */
2459 /* ~/.<program>/levelsetup.conf */
2460 /* ----------------------------------------------------------------------- */
2462 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2464 if ((level_setup_hash = loadSetupFileHash(filename)))
2466 char *last_level_series =
2467 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2469 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2471 if (leveldir_current == NULL)
2472 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2474 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2476 freeSetupFileHash(level_setup_hash);
2479 Error(ERR_WARN, "using default setup values");
2484 void SaveLevelSetup_LastSeries()
2487 char *level_subdir = leveldir_current->filename;
2490 /* ----------------------------------------------------------------------- */
2491 /* ~/.<program>/levelsetup.conf */
2492 /* ----------------------------------------------------------------------- */
2494 InitUserDataDirectory();
2496 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2498 if (!(file = fopen(filename, MODE_WRITE)))
2500 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2505 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2506 getCookie("LEVELSETUP")));
2507 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2513 SetFilePermissions(filename, PERMS_PRIVATE);
2516 static void checkSeriesInfo()
2518 static char *level_directory = NULL;
2520 struct dirent *dir_entry;
2522 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2524 level_directory = getPath2((leveldir_current->user_defined ?
2525 getUserLevelDir(NULL) :
2526 options.level_directory),
2527 leveldir_current->fullpath);
2529 if ((dir = opendir(level_directory)) == NULL)
2531 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2535 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
2537 if (strlen(dir_entry->d_name) > 4 &&
2538 dir_entry->d_name[3] == '.' &&
2539 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2541 char levelnum_str[4];
2544 strncpy(levelnum_str, dir_entry->d_name, 3);
2545 levelnum_str[3] = '\0';
2547 levelnum_value = atoi(levelnum_str);
2550 if (levelnum_value < leveldir_current->first_level)
2552 Error(ERR_WARN, "additional level %d found", levelnum_value);
2553 leveldir_current->first_level = levelnum_value;
2555 else if (levelnum_value > leveldir_current->last_level)
2557 Error(ERR_WARN, "additional level %d found", levelnum_value);
2558 leveldir_current->last_level = levelnum_value;
2567 void LoadLevelSetup_SeriesInfo()
2570 SetupFileHash *level_setup_hash = NULL;
2571 char *level_subdir = leveldir_current->filename;
2573 /* always start with reliable default values */
2574 level_nr = leveldir_current->first_level;
2576 checkSeriesInfo(leveldir_current);
2578 /* ----------------------------------------------------------------------- */
2579 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2580 /* ----------------------------------------------------------------------- */
2582 level_subdir = leveldir_current->filename;
2584 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2586 if ((level_setup_hash = loadSetupFileHash(filename)))
2590 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2594 level_nr = atoi(token_value);
2596 if (level_nr < leveldir_current->first_level)
2597 level_nr = leveldir_current->first_level;
2598 if (level_nr > leveldir_current->last_level)
2599 level_nr = leveldir_current->last_level;
2602 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2606 int level_nr = atoi(token_value);
2608 if (level_nr < leveldir_current->first_level)
2609 level_nr = leveldir_current->first_level;
2610 if (level_nr > leveldir_current->last_level + 1)
2611 level_nr = leveldir_current->last_level;
2613 if (leveldir_current->user_defined)
2614 level_nr = leveldir_current->last_level;
2616 leveldir_current->handicap_level = level_nr;
2619 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2621 freeSetupFileHash(level_setup_hash);
2624 Error(ERR_WARN, "using default setup values");
2629 void SaveLevelSetup_SeriesInfo()
2632 char *level_subdir = leveldir_current->filename;
2633 char *level_nr_str = int2str(level_nr, 0);
2634 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2637 /* ----------------------------------------------------------------------- */
2638 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2639 /* ----------------------------------------------------------------------- */
2641 InitLevelSetupDirectory(level_subdir);
2643 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2645 if (!(file = fopen(filename, MODE_WRITE)))
2647 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2652 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2653 getCookie("LEVELSETUP")));
2654 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2656 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2657 handicap_level_str));
2662 SetFilePermissions(filename, PERMS_PRIVATE);