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) == TREE_TYPE_GRAPHICS_DIR ? \
102 GRAPHICSINFO_FILENAME : \
103 (type) == TREE_TYPE_SOUNDS_DIR ? \
104 SOUNDSINFO_FILENAME : \
105 (type) == TREE_TYPE_MUSIC_DIR ? \
106 MUSICINFO_FILENAME : "")
108 #define ARTWORK_DIRECTORY(type) ((type) == TREE_TYPE_GRAPHICS_DIR ? \
109 GRAPHICS_DIRECTORY : \
110 (type) == TREE_TYPE_SOUNDS_DIR ? \
112 (type) == TREE_TYPE_MUSIC_DIR ? \
113 MUSIC_DIRECTORY : "")
115 #define OPTIONS_ARTWORK_DIRECTORY(type) ((type) == TREE_TYPE_GRAPHICS_DIR ? \
116 options.graphics_directory : \
117 (type) == TREE_TYPE_SOUNDS_DIR ? \
118 options.sounds_directory : \
119 (type) == TREE_TYPE_MUSIC_DIR ? \
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 *getDefaultArtworkDir(int type)
273 return (type == TREE_TYPE_GRAPHICS_DIR ?
274 getDefaultGraphicsDir(GRAPHICS_SUBDIR) :
275 type == TREE_TYPE_SOUNDS_DIR ?
276 getDefaultSoundsDir(SOUNDS_SUBDIR) :
277 type == TREE_TYPE_MUSIC_DIR ?
278 getDefaultMusicDir(MUSIC_SUBDIR) : "");
281 static char *getUserGraphicsDir()
283 static char *usergraphics_dir = NULL;
285 if (usergraphics_dir == NULL)
286 usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
288 return usergraphics_dir;
291 static char *getUserSoundsDir()
293 static char *usersounds_dir = NULL;
295 if (usersounds_dir == NULL)
296 usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
298 return usersounds_dir;
301 static char *getUserMusicDir()
303 static char *usermusic_dir = NULL;
305 if (usermusic_dir == NULL)
306 usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
308 return usermusic_dir;
311 static char *getSetupArtworkDir(TreeInfo *ti)
313 static char *artwork_dir = NULL;
315 if (artwork_dir != NULL)
318 artwork_dir = getPath2(ti->basepath, ti->fullpath);
323 void setLevelArtworkDir(TreeInfo *ti)
325 char **artwork_path_ptr, *artwork_set;
326 TreeInfo *level_artwork;
328 if (ti == NULL || leveldir_current == NULL)
332 (ti->type == TREE_TYPE_GRAPHICS_DIR ? &leveldir_current->graphics_path :
333 ti->type == TREE_TYPE_SOUNDS_DIR ? &leveldir_current->sounds_path :
334 &leveldir_current->music_path);
337 (ti->type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_set :
338 ti->type == TREE_TYPE_SOUNDS_DIR ? leveldir_current->sounds_set :
339 leveldir_current->music_set);
341 if (*artwork_path_ptr != NULL)
342 free(*artwork_path_ptr);
344 if ((level_artwork = getTreeInfoFromIdentifier(ti, artwork_set)))
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));
358 *artwork_path_ptr = getStringCopy(getDefaultArtworkDir(ti->type));
360 *artwork_path_ptr = getStringCopy(UNDEFINED_FILENAME);
366 static char *getLevelArtworkDir(int type)
370 if (leveldir_current == NULL)
371 return UNDEFINED_FILENAME;
374 (type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_path :
375 type == TREE_TYPE_SOUNDS_DIR ? leveldir_current->sounds_path :
376 type == TREE_TYPE_MUSIC_DIR ? leveldir_current->music_path :
382 char *getLevelFilename(int nr)
384 static char *filename = NULL;
385 char basename[MAX_FILENAME_LEN];
387 if (filename != NULL)
391 sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
393 sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
395 filename = getPath2(getCurrentLevelDir(), basename);
400 char *getTapeFilename(int nr)
402 static char *filename = NULL;
403 char basename[MAX_FILENAME_LEN];
405 if (filename != NULL)
408 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
409 filename = getPath2(getTapeDir(leveldir_current->filename), basename);
414 char *getScoreFilename(int nr)
416 static char *filename = NULL;
417 char basename[MAX_FILENAME_LEN];
419 if (filename != NULL)
422 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
423 filename = getPath2(getScoreDir(leveldir_current->filename), basename);
428 char *getSetupFilename()
430 static char *filename = NULL;
432 if (filename != NULL)
435 filename = getPath2(getSetupDir(), SETUP_FILENAME);
440 static char *getCorrectedImageBasename(char *basename)
442 char *basename_corrected = basename;
444 #if defined(PLATFORM_MSDOS)
445 if (program.filename_prefix != NULL)
447 int prefix_len = strlen(program.filename_prefix);
449 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
450 basename_corrected = &basename[prefix_len];
452 /* if corrected filename is still longer than standard MS-DOS filename
453 size (8 characters + 1 dot + 3 characters file extension), shorten
454 filename by writing file extension after 8th basename character */
455 if (strlen(basename_corrected) > 8+1+3)
457 static char *msdos_filename = NULL;
459 if (msdos_filename != NULL)
460 free(msdos_filename);
462 msdos_filename = getStringCopy(basename_corrected);
463 strncpy(&msdos_filename[8], &basename[strlen(basename) - 1+3], 1+3 + 1);
468 return basename_corrected;
471 char *getCustomImageFilename(char *basename)
473 static char *filename = NULL;
475 if (filename != NULL)
478 basename = getCorrectedImageBasename(basename);
480 if (!setup.override_level_graphics)
483 /* 1st try: look for special artwork configured in level series config */
484 filename = getPath2(getLevelArtworkDir(TREE_TYPE_GRAPHICS_DIR), basename);
486 if (strcmp(basename, "RocksScreen.pcx") == 0)
487 printf("::: trying 1 '%s' ...\n", filename);
489 if (fileExists(filename))
495 /* 2nd try: look for special artwork in current level series directory */
496 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
498 if (strcmp(basename, "RocksScreen.pcx") == 0)
499 printf("::: trying 2 '%s' ...\n", filename);
501 if (fileExists(filename))
507 /* 1st try: look for special artwork configured in level series config */
508 filename = getPath2(getLevelArtworkDir(TREE_TYPE_GRAPHICS_DIR), basename);
510 if (strcmp(basename, "RocksScreen.pcx") == 0)
511 printf("::: trying 2.1 '%s' ...\n", filename);
513 if (fileExists(filename))
520 /* 3rd try: look for special artwork in configured artwork directory */
521 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
523 if (strcmp(basename, "RocksScreen.pcx") == 0)
524 printf("::: trying 3 '%s' ...\n", filename);
526 if (fileExists(filename))
531 /* 4th try: look for default artwork in new default artwork directory */
532 filename = getPath2(getDefaultGraphicsDir(GRAPHICS_SUBDIR), basename);
534 if (strcmp(basename, "RocksScreen.pcx") == 0)
535 printf("::: trying 4 '%s' ...\n", filename);
537 if (fileExists(filename))
542 /* 5th try: look for default artwork in old default artwork directory */
543 filename = getPath2(options.graphics_directory, basename);
545 if (strcmp(basename, "RocksScreen.pcx") == 0)
546 printf("::: trying 5 '%s' ...\n", filename);
548 if (fileExists(filename))
551 return NULL; /* cannot find specified artwork file anywhere */
554 char *getCustomSoundFilename(char *basename)
556 static char *filename = NULL;
558 if (filename != NULL)
561 if (!setup.override_level_sounds)
564 /* 1st try: look for special artwork configured in level series config */
565 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
566 if (fileExists(filename))
572 /* 2nd try: look for special artwork in current level series directory */
573 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
574 if (fileExists(filename))
580 /* 1st try: look for special artwork configured in level series config */
581 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
582 if (fileExists(filename))
589 /* 3rd try: look for special artwork in configured artwork directory */
590 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
591 if (fileExists(filename))
596 /* 4th try: look for default artwork in new default artwork directory */
597 filename = getPath2(getDefaultSoundsDir(SOUNDS_SUBDIR), basename);
598 if (fileExists(filename))
603 /* 5th try: look for default artwork in old default artwork directory */
604 filename = getPath2(options.sounds_directory, basename);
605 if (fileExists(filename))
608 return NULL; /* cannot find specified artwork file anywhere */
611 char *getCustomArtworkFilename(char *basename, int type)
613 if (type == ARTWORK_TYPE_GRAPHICS)
614 return getCustomImageFilename(basename);
615 else if (type == ARTWORK_TYPE_SOUNDS)
616 return getCustomSoundFilename(basename);
618 return UNDEFINED_FILENAME;
621 char *getCustomArtworkConfigFilename(int type)
623 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
626 char *getCustomArtworkLevelConfigFilename(int type)
628 static char *filename = NULL;
630 if (filename != NULL)
633 filename = getPath2(getLevelArtworkDir(type), ARTWORKINFO_FILENAME(type));
638 char *getCustomMusicDirectory(void)
640 static char *directory = NULL;
642 if (directory != NULL)
645 if (!setup.override_level_music)
648 /* 1st try: look for special artwork configured in level series config */
649 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
650 if (fileExists(directory))
656 /* 2nd try: look for special artwork in current level series directory */
657 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
658 if (fileExists(directory))
664 /* 1st try: look for special artwork configured in level series config */
665 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
666 if (fileExists(directory))
673 /* 3rd try: look for special artwork in configured artwork directory */
674 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
675 if (fileExists(directory))
680 /* 4th try: look for default artwork in new default artwork directory */
681 directory = getStringCopy(getDefaultMusicDir(MUSIC_SUBDIR));
682 if (fileExists(directory))
687 /* 5th try: look for default artwork in old default artwork directory */
688 directory = getStringCopy(options.music_directory);
689 if (fileExists(directory))
692 return NULL; /* cannot find specified artwork file anywhere */
695 void InitTapeDirectory(char *level_subdir)
697 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
698 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
699 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
702 void InitScoreDirectory(char *level_subdir)
704 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
705 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
706 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
709 static void SaveUserLevelInfo();
711 void InitUserLevelDirectory(char *level_subdir)
713 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
715 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
716 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
717 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
723 void InitLevelSetupDirectory(char *level_subdir)
725 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
726 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
727 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
731 /* ------------------------------------------------------------------------- */
732 /* some functions to handle lists of level directories */
733 /* ------------------------------------------------------------------------- */
735 TreeInfo *newTreeInfo()
737 return checked_calloc(sizeof(TreeInfo));
740 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
742 node_new->next = *node_first;
743 *node_first = node_new;
746 int numTreeInfo(TreeInfo *node)
759 boolean validLevelSeries(TreeInfo *node)
761 return (node != NULL && !node->node_group && !node->parent_link);
764 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
769 if (node->node_group) /* enter level group (step down into tree) */
770 return getFirstValidTreeInfoEntry(node->node_group);
771 else if (node->parent_link) /* skip start entry of level group */
773 if (node->next) /* get first real level series entry */
774 return getFirstValidTreeInfoEntry(node->next);
775 else /* leave empty level group and go on */
776 return getFirstValidTreeInfoEntry(node->node_parent->next);
778 else /* this seems to be a regular level series */
782 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
787 if (node->node_parent == NULL) /* top level group */
788 return *node->node_top;
789 else /* sub level group */
790 return node->node_parent->node_group;
793 int numTreeInfoInGroup(TreeInfo *node)
795 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
798 int posTreeInfo(TreeInfo *node)
800 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
805 if (node_cmp == node)
809 node_cmp = node_cmp->next;
815 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
817 TreeInfo *node_default = node;
832 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
834 if (identifier == NULL)
839 if (node->node_group)
841 TreeInfo *node_group;
843 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
848 else if (!node->parent_link)
850 if (strcmp(identifier, node->identifier) == 0)
860 void dumpTreeInfo(TreeInfo *node, int depth)
864 printf("Dumping TreeInfo:\n");
868 for (i=0; i<(depth + 1) * 3; i++)
871 printf("filename == '%s' (%s) [%s] (%d)\n",
872 node->filename, node->name, node->identifier, node->sort_priority);
874 if (node->node_group != NULL)
875 dumpTreeInfo(node->node_group, depth + 1);
881 void sortTreeInfo(TreeInfo **node_first,
882 int (*compare_function)(const void *, const void *))
884 int num_nodes = numTreeInfo(*node_first);
885 TreeInfo **sort_array;
886 TreeInfo *node = *node_first;
892 /* allocate array for sorting structure pointers */
893 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
895 /* writing structure pointers to sorting array */
896 while (i < num_nodes && node) /* double boundary check... */
898 sort_array[i] = node;
904 /* sorting the structure pointers in the sorting array */
905 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
908 /* update the linkage of list elements with the sorted node array */
909 for (i=0; i<num_nodes - 1; i++)
910 sort_array[i]->next = sort_array[i + 1];
911 sort_array[num_nodes - 1]->next = NULL;
913 /* update the linkage of the main list anchor pointer */
914 *node_first = sort_array[0];
918 /* now recursively sort the level group structures */
922 if (node->node_group != NULL)
923 sortTreeInfo(&node->node_group, compare_function);
930 /* ========================================================================= */
931 /* some stuff from "files.c" */
932 /* ========================================================================= */
934 #if defined(PLATFORM_WIN32)
936 #define S_IRGRP S_IRUSR
939 #define S_IROTH S_IRUSR
942 #define S_IWGRP S_IWUSR
945 #define S_IWOTH S_IWUSR
948 #define S_IXGRP S_IXUSR
951 #define S_IXOTH S_IXUSR
954 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
959 #endif /* PLATFORM_WIN32 */
961 /* file permissions for newly written files */
962 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
963 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
964 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
966 #define MODE_W_PRIVATE (S_IWUSR)
967 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
968 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
970 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
971 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
973 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
974 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
976 char *getUserDataDir(void)
978 static char *userdata_dir = NULL;
980 if (userdata_dir == NULL)
981 userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
986 char *getCommonDataDir(void)
988 static char *common_data_dir = NULL;
990 #if defined(PLATFORM_WIN32)
991 if (common_data_dir == NULL)
993 char *dir = checked_malloc(MAX_PATH + 1);
995 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
996 && strcmp(dir, "") != 0) /* empty for Windows 95/98 */
997 common_data_dir = getPath2(dir, program.userdata_directory);
999 common_data_dir = options.rw_base_directory;
1002 if (common_data_dir == NULL)
1003 common_data_dir = options.rw_base_directory;
1006 return common_data_dir;
1011 return getUserDataDir();
1014 static mode_t posix_umask(mode_t mask)
1016 #if defined(PLATFORM_UNIX)
1023 static int posix_mkdir(const char *pathname, mode_t mode)
1025 #if defined(PLATFORM_WIN32)
1026 return mkdir(pathname);
1028 return mkdir(pathname, mode);
1032 void createDirectory(char *dir, char *text, int permission_class)
1034 /* leave "other" permissions in umask untouched, but ensure group parts
1035 of USERDATA_DIR_MODE are not masked */
1036 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
1037 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
1038 mode_t normal_umask = posix_umask(0);
1039 mode_t group_umask = ~(dir_mode & S_IRWXG);
1040 posix_umask(normal_umask & group_umask);
1042 if (access(dir, F_OK) != 0)
1043 if (posix_mkdir(dir, dir_mode) != 0)
1044 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
1046 posix_umask(normal_umask); /* reset normal umask */
1049 void InitUserDataDirectory()
1051 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
1054 void SetFilePermissions(char *filename, int permission_class)
1056 chmod(filename, (permission_class == PERMS_PRIVATE ?
1057 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
1060 char *getCookie(char *file_type)
1062 static char cookie[MAX_COOKIE_LEN + 1];
1064 if (strlen(program.cookie_prefix) + 1 +
1065 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
1066 return "[COOKIE ERROR]"; /* should never happen */
1068 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
1069 program.cookie_prefix, file_type,
1070 program.version_major, program.version_minor);
1075 int getFileVersionFromCookieString(const char *cookie)
1077 const char *ptr_cookie1, *ptr_cookie2;
1078 const char *pattern1 = "_FILE_VERSION_";
1079 const char *pattern2 = "?.?";
1080 const int len_cookie = strlen(cookie);
1081 const int len_pattern1 = strlen(pattern1);
1082 const int len_pattern2 = strlen(pattern2);
1083 const int len_pattern = len_pattern1 + len_pattern2;
1084 int version_major, version_minor;
1086 if (len_cookie <= len_pattern)
1089 ptr_cookie1 = &cookie[len_cookie - len_pattern];
1090 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
1092 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1095 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1096 ptr_cookie2[1] != '.' ||
1097 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1100 version_major = ptr_cookie2[0] - '0';
1101 version_minor = ptr_cookie2[2] - '0';
1103 return VERSION_IDENT(version_major, version_minor, 0);
1106 boolean checkCookieString(const char *cookie, const char *template)
1108 const char *pattern = "_FILE_VERSION_?.?";
1109 const int len_cookie = strlen(cookie);
1110 const int len_template = strlen(template);
1111 const int len_pattern = strlen(pattern);
1113 if (len_cookie != len_template)
1116 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1122 /* ------------------------------------------------------------------------- */
1123 /* setup file list and hash handling functions */
1124 /* ------------------------------------------------------------------------- */
1126 char *getFormattedSetupEntry(char *token, char *value)
1129 static char entry[MAX_LINE_LEN];
1131 /* start with the token and some spaces to format output line */
1132 sprintf(entry, "%s:", token);
1133 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1136 /* continue with the token's value */
1137 strcat(entry, value);
1142 SetupFileList *newSetupFileList(char *token, char *value)
1144 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1146 new->token = getStringCopy(token);
1147 new->value = getStringCopy(value);
1154 void freeSetupFileList(SetupFileList *list)
1164 freeSetupFileList(list->next);
1168 char *getListEntry(SetupFileList *list, char *token)
1173 if (strcmp(list->token, token) == 0)
1176 return getListEntry(list->next, token);
1179 void setListEntry(SetupFileList *list, char *token, char *value)
1184 if (strcmp(list->token, token) == 0)
1189 list->value = getStringCopy(value);
1191 else if (list->next == NULL)
1192 list->next = newSetupFileList(token, value);
1194 setListEntry(list->next, token, value);
1198 static void printSetupFileList(SetupFileList *list)
1203 printf("token: '%s'\n", list->token);
1204 printf("value: '%s'\n", list->value);
1206 printSetupFileList(list->next);
1211 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1212 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1213 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1214 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1216 #define insert_hash_entry hashtable_insert
1217 #define search_hash_entry hashtable_search
1218 #define change_hash_entry hashtable_change
1219 #define remove_hash_entry hashtable_remove
1222 static unsigned int get_hash_from_key(void *key)
1227 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1228 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1229 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1230 it works better than many other constants, prime or not) has never been
1231 adequately explained.
1233 If you just want to have a good hash function, and cannot wait, djb2
1234 is one of the best string hash functions i know. It has excellent
1235 distribution and speed on many different sets of keys and table sizes.
1236 You are not likely to do better with one of the "well known" functions
1237 such as PJW, K&R, etc.
1239 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1242 char *str = (char *)key;
1243 unsigned int hash = 5381;
1246 while ((c = *str++))
1247 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1252 static int keys_are_equal(void *key1, void *key2)
1254 return (strcmp((char *)key1, (char *)key2) == 0);
1257 SetupFileHash *newSetupFileHash()
1259 SetupFileHash *new_hash =
1260 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1265 void freeSetupFileHash(SetupFileHash *hash)
1270 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1273 char *getHashEntry(SetupFileHash *hash, char *token)
1278 return search_hash_entry(hash, token);
1281 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1288 value_copy = getStringCopy(value);
1290 /* change value; if it does not exist, insert it as new */
1291 if (!change_hash_entry(hash, token, value_copy))
1292 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1293 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1298 static void printSetupFileHash(SetupFileHash *hash)
1300 BEGIN_HASH_ITERATION(hash, itr)
1302 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1303 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1305 END_HASH_ITERATION(hash, itr)
1310 static void *loadSetupFileData(char *filename, boolean use_hash)
1313 char line[MAX_LINE_LEN];
1314 char *token, *value, *line_ptr;
1315 void *setup_file_data;
1319 setup_file_data = newSetupFileHash();
1321 setup_file_data = newSetupFileList("", "");
1323 if (!(file = fopen(filename, MODE_READ)))
1325 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1331 /* read next line of input file */
1332 if (!fgets(line, MAX_LINE_LEN, file))
1335 /* cut trailing comment or whitespace from input line */
1336 for (line_ptr = line; *line_ptr; line_ptr++)
1338 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1345 /* cut trailing whitespaces from input line */
1346 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1347 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1350 /* ignore empty lines */
1354 line_len = strlen(line);
1356 /* cut leading whitespaces from token */
1357 for (token = line; *token; token++)
1358 if (*token != ' ' && *token != '\t')
1361 /* find end of token */
1362 for (line_ptr = token; *line_ptr; line_ptr++)
1364 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1371 if (line_ptr < line + line_len)
1372 value = line_ptr + 1;
1376 /* cut leading whitespaces from value */
1377 for (; *value; value++)
1378 if (*value != ' ' && *value != '\t')
1381 if (*token && *value)
1384 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1386 setListEntry((SetupFileList *)setup_file_data, token, value);
1394 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1395 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1399 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1400 SetupFileList *first_valid_list_entry = setup_file_list->next;
1402 /* free empty list header */
1403 setup_file_list->next = NULL;
1404 freeSetupFileList(setup_file_list);
1405 setup_file_data = first_valid_list_entry;
1407 if (first_valid_list_entry == NULL)
1408 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1411 return setup_file_data;
1414 SetupFileList *loadSetupFileList(char *filename)
1416 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1419 SetupFileHash *loadSetupFileHash(char *filename)
1421 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1424 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1427 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1430 Error(ERR_WARN, "configuration file has no file identifier");
1431 else if (!checkCookieString(value, identifier))
1432 Error(ERR_WARN, "configuration file has wrong file identifier");
1436 /* ========================================================================= */
1437 /* setup file stuff */
1438 /* ========================================================================= */
1440 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1441 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1442 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1444 /* level directory info */
1445 #define LEVELINFO_TOKEN_IDENTIFIER 0
1446 #define LEVELINFO_TOKEN_NAME 1
1447 #define LEVELINFO_TOKEN_NAME_SORTING 2
1448 #define LEVELINFO_TOKEN_AUTHOR 3
1449 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1450 #define LEVELINFO_TOKEN_LEVELS 5
1451 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
1452 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
1453 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
1454 #define LEVELINFO_TOKEN_READONLY 9
1455 #define LEVELINFO_TOKEN_GRAPHICS_SET 10
1456 #define LEVELINFO_TOKEN_SOUNDS_SET 11
1457 #define LEVELINFO_TOKEN_MUSIC_SET 12
1459 #define NUM_LEVELINFO_TOKENS 13
1461 static LevelDirTree ldi;
1463 static struct TokenInfo levelinfo_tokens[] =
1465 /* level directory info */
1466 { TYPE_STRING, &ldi.identifier, "identifier" },
1467 { TYPE_STRING, &ldi.name, "name" },
1468 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1469 { TYPE_STRING, &ldi.author, "author" },
1470 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1471 { TYPE_INTEGER, &ldi.levels, "levels" },
1472 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1473 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1474 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1475 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1476 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1477 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1478 { TYPE_STRING, &ldi.music_set, "music_set" }
1481 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1485 ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1486 ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1487 ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1488 ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1491 ldi->node_parent = NULL;
1492 ldi->node_group = NULL;
1496 ldi->cl_cursor = -1;
1498 ldi->filename = NULL;
1499 ldi->fullpath = NULL;
1500 ldi->basepath = NULL;
1501 ldi->identifier = NULL;
1502 ldi->name = getStringCopy(ANONYMOUS_NAME);
1503 ldi->name_sorting = NULL;
1504 ldi->author = getStringCopy(ANONYMOUS_NAME);
1506 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1507 ldi->parent_link = FALSE;
1508 ldi->user_defined = FALSE;
1510 ldi->class_desc = NULL;
1512 if (ldi->type == TREE_TYPE_LEVEL_DIR)
1514 ldi->imported_from = NULL;
1515 ldi->graphics_set = NULL;
1516 ldi->sounds_set = NULL;
1517 ldi->music_set = NULL;
1518 ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1519 ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1520 ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1522 ldi->first_level = 0;
1523 ldi->last_level = 0;
1524 ldi->level_group = FALSE;
1525 ldi->handicap_level = 0;
1526 ldi->readonly = TRUE;
1530 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1534 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1536 setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1540 /* first copy all values from the parent structure ... */
1543 /* ... then set all fields to default that cannot be inherited from parent.
1544 This is especially important for all those fields that can be set from
1545 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1546 calls 'free()' for all already set token values which requires that no
1547 other structure's pointer may point to them!
1550 ldi->filename = NULL;
1551 ldi->fullpath = NULL;
1552 ldi->basepath = NULL;
1553 ldi->identifier = NULL;
1554 ldi->name = getStringCopy(ANONYMOUS_NAME);
1555 ldi->name_sorting = NULL;
1556 ldi->author = getStringCopy(parent->author);
1557 ldi->imported_from = getStringCopy(parent->imported_from);
1559 ldi->level_group = FALSE;
1560 ldi->parent_link = FALSE;
1562 ldi->node_top = parent->node_top;
1563 ldi->node_parent = parent;
1564 ldi->node_group = NULL;
1568 void setSetupInfo(struct TokenInfo *token_info,
1569 int token_nr, char *token_value)
1571 int token_type = token_info[token_nr].type;
1572 void *setup_value = token_info[token_nr].value;
1574 if (token_value == NULL)
1577 /* set setup field to corresponding token value */
1582 *(boolean *)setup_value = get_boolean_from_string(token_value);
1586 *(Key *)setup_value = getKeyFromKeyName(token_value);
1590 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1594 *(int *)setup_value = get_integer_from_string(token_value);
1598 if (*(char **)setup_value != NULL)
1599 free(*(char **)setup_value);
1600 *(char **)setup_value = getStringCopy(token_value);
1608 static int compareTreeInfoEntries(const void *object1, const void *object2)
1610 const TreeInfo *entry1 = *((TreeInfo **)object1);
1611 const TreeInfo *entry2 = *((TreeInfo **)object2);
1612 int class_sorting1, class_sorting2;
1615 if (entry1->type == TREE_TYPE_LEVEL_DIR)
1617 class_sorting1 = LEVELSORTING(entry1);
1618 class_sorting2 = LEVELSORTING(entry2);
1622 class_sorting1 = ARTWORKSORTING(entry1);
1623 class_sorting2 = ARTWORKSORTING(entry2);
1626 if (entry1->parent_link || entry2->parent_link)
1627 compare_result = (entry1->parent_link ? -1 : +1);
1628 else if (entry1->sort_priority == entry2->sort_priority)
1630 char *name1 = getStringToLower(entry1->name_sorting);
1631 char *name2 = getStringToLower(entry2->name_sorting);
1633 compare_result = strcmp(name1, name2);
1638 else if (class_sorting1 == class_sorting2)
1639 compare_result = entry1->sort_priority - entry2->sort_priority;
1641 compare_result = class_sorting1 - class_sorting2;
1643 return compare_result;
1646 static void createParentTreeInfoNode(TreeInfo *node_parent)
1650 if (node_parent == NULL)
1653 ti_new = newTreeInfo();
1654 setTreeInfoToDefaults(ti_new, node_parent->type);
1656 ti_new->node_parent = node_parent;
1657 ti_new->parent_link = TRUE;
1659 ti_new->identifier = getStringCopy(node_parent->identifier);
1660 ti_new->name = ".. (parent directory)";
1661 ti_new->name_sorting = getStringCopy(ti_new->name);
1663 ti_new->filename = "..";
1664 ti_new->fullpath = getStringCopy(node_parent->fullpath);
1666 ti_new->sort_priority = node_parent->sort_priority;
1667 ti_new->class_desc = getLevelClassDescription(ti_new);
1669 pushTreeInfo(&node_parent->node_group, ti_new);
1672 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1673 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1675 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1676 TreeInfo *node_parent,
1677 char *level_directory,
1678 char *directory_name)
1680 char *directory_path = getPath2(level_directory, directory_name);
1681 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1682 SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1683 LevelDirTree *leveldir_new = NULL;
1686 if (setup_file_hash == NULL)
1688 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1690 free(directory_path);
1696 leveldir_new = newTreeInfo();
1699 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1701 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1703 leveldir_new->filename = getStringCopy(directory_name);
1705 checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1707 /* set all structure fields according to the token/value pairs */
1708 ldi = *leveldir_new;
1709 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1710 setSetupInfo(levelinfo_tokens, i,
1711 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1712 *leveldir_new = ldi;
1714 if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1716 free(leveldir_new->name);
1717 leveldir_new->name = getStringCopy(leveldir_new->filename);
1720 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1722 if (leveldir_new->identifier == NULL)
1723 leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1725 if (leveldir_new->name_sorting == NULL)
1726 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1728 if (node_parent == NULL) /* top level group */
1730 leveldir_new->basepath = level_directory;
1731 leveldir_new->fullpath = leveldir_new->filename;
1733 else /* sub level group */
1735 leveldir_new->basepath = node_parent->basepath;
1736 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1739 if (leveldir_new->levels < 1)
1740 leveldir_new->levels = 1;
1742 leveldir_new->last_level =
1743 leveldir_new->first_level + leveldir_new->levels - 1;
1745 leveldir_new->user_defined =
1746 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1748 leveldir_new->color = LEVELCOLOR(leveldir_new);
1749 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1751 leveldir_new->handicap_level = /* set handicap to default value */
1752 (leveldir_new->user_defined ?
1753 leveldir_new->last_level :
1754 leveldir_new->first_level);
1756 pushTreeInfo(node_first, leveldir_new);
1758 freeSetupFileHash(setup_file_hash);
1760 if (leveldir_new->level_group)
1762 /* create node to link back to current level directory */
1763 createParentTreeInfoNode(leveldir_new);
1765 /* step into sub-directory and look for more level series */
1766 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1767 leveldir_new, directory_path);
1770 free(directory_path);
1776 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1777 TreeInfo *node_parent,
1778 char *level_directory)
1781 struct dirent *dir_entry;
1782 boolean valid_entry_found = FALSE;
1784 if ((dir = opendir(level_directory)) == NULL)
1786 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1790 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1792 struct stat file_status;
1793 char *directory_name = dir_entry->d_name;
1794 char *directory_path = getPath2(level_directory, directory_name);
1796 /* skip entries for current and parent directory */
1797 if (strcmp(directory_name, ".") == 0 ||
1798 strcmp(directory_name, "..") == 0)
1800 free(directory_path);
1804 /* find out if directory entry is itself a directory */
1805 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1806 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1808 free(directory_path);
1812 free(directory_path);
1814 if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1815 strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1816 strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1819 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1826 if (!valid_entry_found)
1828 /* check if this directory directly contains a file "levelinfo.conf" */
1829 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1830 level_directory, ".");
1833 if (!valid_entry_found)
1834 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1838 void LoadLevelInfo()
1840 InitUserLevelDirectory(getLoginName());
1842 DrawInitText("Loading level series:", 120, FC_GREEN);
1844 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1845 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1847 /* before sorting, the first entries will be from the user directory */
1848 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1850 if (leveldir_first == NULL)
1851 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1853 sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1856 dumpTreeInfo(leveldir_first, 0);
1860 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1861 TreeInfo *node_parent,
1862 char *base_directory,
1863 char *directory_name, int type)
1865 char *directory_path = getPath2(base_directory, directory_name);
1866 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1867 SetupFileHash *setup_file_hash = NULL;
1868 TreeInfo *artwork_new = NULL;
1871 if (access(filename, F_OK) == 0) /* file exists */
1872 setup_file_hash = loadSetupFileHash(filename);
1874 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
1877 struct dirent *dir_entry;
1878 boolean valid_file_found = FALSE;
1880 if ((dir = opendir(directory_path)) != NULL)
1882 while ((dir_entry = readdir(dir)) != NULL)
1884 char *entry_name = dir_entry->d_name;
1886 if (FileIsArtworkType(entry_name, type))
1888 valid_file_found = TRUE;
1896 if (!valid_file_found)
1898 if (strcmp(directory_name, ".") != 0)
1899 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1901 free(directory_path);
1908 artwork_new = newTreeInfo();
1911 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1913 setTreeInfoToDefaults(artwork_new, type);
1915 artwork_new->filename = getStringCopy(directory_name);
1917 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
1920 checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
1923 /* set all structure fields according to the token/value pairs */
1925 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1926 setSetupInfo(levelinfo_tokens, i,
1927 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1930 if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1932 free(artwork_new->name);
1933 artwork_new->name = getStringCopy(artwork_new->filename);
1937 DrawInitText(artwork_new->name, 150, FC_YELLOW);
1940 if (artwork_new->identifier == NULL)
1941 artwork_new->identifier = getStringCopy(artwork_new->filename);
1943 if (artwork_new->name_sorting == NULL)
1944 artwork_new->name_sorting = getStringCopy(artwork_new->name);
1947 if (node_parent == NULL) /* top level group */
1949 artwork_new->basepath = getStringCopy(base_directory);
1950 artwork_new->fullpath = getStringCopy(artwork_new->filename);
1952 else /* sub level group */
1954 artwork_new->basepath = getStringCopy(node_parent->basepath);
1955 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1958 artwork_new->user_defined =
1959 (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
1961 /* (may use ".sort_priority" from "setup_file_hash" above) */
1962 artwork_new->color = ARTWORKCOLOR(artwork_new);
1963 artwork_new->class_desc = getLevelClassDescription(artwork_new);
1965 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
1967 if (artwork_new->name != NULL)
1968 free(artwork_new->name);
1970 if (strcmp(artwork_new->filename, ".") == 0)
1972 if (artwork_new->user_defined)
1974 artwork_new->identifier = getStringCopy("private");
1975 artwork_new->sort_priority = ARTWORKCLASS_USER;
1979 artwork_new->identifier = getStringCopy("classic");
1980 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
1983 /* set to new values after changing ".sort_priority" */
1984 artwork_new->color = ARTWORKCOLOR(artwork_new);
1985 artwork_new->class_desc = getLevelClassDescription(artwork_new);
1989 artwork_new->identifier = getStringCopy(artwork_new->filename);
1992 artwork_new->name = getStringCopy(artwork_new->identifier);
1993 artwork_new->name_sorting = getStringCopy(artwork_new->name);
1996 DrawInitText(artwork_new->name, 150, FC_YELLOW);
1998 pushTreeInfo(node_first, artwork_new);
2000 freeSetupFileHash(setup_file_hash);
2002 free(directory_path);
2008 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
2009 TreeInfo *node_parent,
2010 char *base_directory, int type)
2013 struct dirent *dir_entry;
2014 boolean valid_entry_found = FALSE;
2016 if ((dir = opendir(base_directory)) == NULL)
2018 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
2019 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
2023 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
2025 struct stat file_status;
2026 char *directory_name = dir_entry->d_name;
2027 char *directory_path = getPath2(base_directory, directory_name);
2029 /* skip entries for current and parent directory */
2030 if (strcmp(directory_name, ".") == 0 ||
2031 strcmp(directory_name, "..") == 0)
2033 free(directory_path);
2037 /* find out if directory entry is itself a directory */
2038 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
2039 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
2041 free(directory_path);
2045 free(directory_path);
2047 /* check if this directory contains artwork with or without config file */
2048 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2050 directory_name, type);
2055 /* check if this directory directly contains artwork itself */
2056 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
2057 base_directory, ".",
2059 if (!valid_entry_found)
2060 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
2064 static TreeInfo *getDummyArtworkInfo(int type)
2066 /* this is only needed when there is completely no artwork available */
2067 TreeInfo *artwork_new = newTreeInfo();
2069 setTreeInfoToDefaults(artwork_new, type);
2071 artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
2072 artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
2073 artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
2075 if (artwork_new->name != NULL)
2076 free(artwork_new->name);
2078 artwork_new->identifier = getStringCopy(UNDEFINED_FILENAME);
2079 artwork_new->name = getStringCopy(UNDEFINED_FILENAME);
2080 artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
2085 void LoadArtworkInfo()
2087 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
2089 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2090 options.graphics_directory,
2091 TREE_TYPE_GRAPHICS_DIR);
2092 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
2093 getUserGraphicsDir(),
2094 TREE_TYPE_GRAPHICS_DIR);
2096 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2097 options.sounds_directory,
2098 TREE_TYPE_SOUNDS_DIR);
2099 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2101 TREE_TYPE_SOUNDS_DIR);
2103 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2104 options.music_directory,
2105 TREE_TYPE_MUSIC_DIR);
2106 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2108 TREE_TYPE_MUSIC_DIR);
2110 if (artwork.gfx_first == NULL)
2111 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2112 if (artwork.snd_first == NULL)
2113 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2114 if (artwork.mus_first == NULL)
2115 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2117 /* before sorting, the first entries will be from the user directory */
2118 artwork.gfx_current =
2119 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2120 if (artwork.gfx_current == NULL)
2121 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2123 artwork.snd_current =
2124 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2125 if (artwork.snd_current == NULL)
2126 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2128 artwork.mus_current =
2129 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2130 if (artwork.mus_current == NULL)
2131 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2133 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2134 artwork.snd_current_identifier = artwork.snd_current->identifier;
2135 artwork.mus_current_identifier = artwork.mus_current->identifier;
2138 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2139 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2140 printf("music set == %s\n\n", artwork.mus_current_identifier);
2143 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2144 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2145 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2148 dumpTreeInfo(artwork.gfx_first, 0);
2149 dumpTreeInfo(artwork.snd_first, 0);
2150 dumpTreeInfo(artwork.mus_first, 0);
2154 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2155 LevelDirTree *level_node)
2157 /* recursively check all level directories for artwork sub-directories */
2161 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2162 ARTWORK_DIRECTORY((*artwork_node)->type));
2165 if (!level_node->parent_link)
2166 printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2167 level_node->filename, level_node->name);
2170 if (!level_node->parent_link)
2172 TreeInfo *topnode_last = *artwork_node;
2174 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2175 (*artwork_node)->type);
2177 if (topnode_last != *artwork_node)
2179 free((*artwork_node)->identifier);
2180 free((*artwork_node)->name);
2181 free((*artwork_node)->name_sorting);
2183 (*artwork_node)->identifier = getStringCopy(level_node->filename);
2184 (*artwork_node)->name = getStringCopy(level_node->name);
2185 (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2187 (*artwork_node)->sort_priority = level_node->sort_priority;
2188 (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2194 if (level_node->node_group != NULL)
2195 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2197 level_node = level_node->next;
2201 void LoadLevelArtworkInfo()
2203 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2205 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2206 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2207 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2209 /* needed for reloading level artwork not known at ealier stage */
2210 if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2212 artwork.gfx_current =
2213 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2214 if (artwork.gfx_current == NULL)
2215 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2218 if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2220 artwork.snd_current =
2221 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2222 if (artwork.snd_current == NULL)
2223 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2226 if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2228 artwork.mus_current =
2229 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2230 if (artwork.mus_current == NULL)
2231 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2234 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2235 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2236 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2239 dumpTreeInfo(artwork.gfx_first, 0);
2240 dumpTreeInfo(artwork.snd_first, 0);
2241 dumpTreeInfo(artwork.mus_first, 0);
2245 static void SaveUserLevelInfo()
2251 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2253 if (!(file = fopen(filename, MODE_WRITE)))
2255 Error(ERR_WARN, "cannot write level info file '%s'", filename);
2260 /* always start with reliable default values */
2261 setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2263 ldi.name = getStringCopy(getLoginName());
2264 ldi.author = getStringCopy(getRealName());
2266 ldi.first_level = 1;
2267 ldi.sort_priority = LEVELCLASS_USER_START;
2268 ldi.readonly = FALSE;
2269 ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2270 ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2271 ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2273 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2274 getCookie("LEVELINFO")));
2276 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2277 if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2278 i != LEVELINFO_TOKEN_NAME_SORTING &&
2279 i != LEVELINFO_TOKEN_IMPORTED_FROM)
2280 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2285 SetFilePermissions(filename, PERMS_PRIVATE);
2288 char *getSetupValue(int type, void *value)
2290 static char value_string[MAX_LINE_LEN];
2298 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2302 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2306 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2310 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2314 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2318 sprintf(value_string, "%d", *(int *)value);
2322 strcpy(value_string, *(char **)value);
2326 value_string[0] = '\0';
2330 return value_string;
2333 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2337 static char token_string[MAX_LINE_LEN];
2338 int token_type = token_info[token_nr].type;
2339 void *setup_value = token_info[token_nr].value;
2340 char *token_text = token_info[token_nr].text;
2341 char *value_string = getSetupValue(token_type, setup_value);
2343 /* build complete token string */
2344 sprintf(token_string, "%s%s", prefix, token_text);
2346 /* build setup entry line */
2347 line = getFormattedSetupEntry(token_string, value_string);
2349 if (token_type == TYPE_KEY_X11)
2351 Key key = *(Key *)setup_value;
2352 char *keyname = getKeyNameFromKey(key);
2354 /* add comment, if useful */
2355 if (strcmp(keyname, "(undefined)") != 0 &&
2356 strcmp(keyname, "(unknown)") != 0)
2358 /* add at least one whitespace */
2360 for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2364 strcat(line, keyname);
2371 void LoadLevelSetup_LastSeries()
2374 SetupFileHash *level_setup_hash = NULL;
2376 /* always start with reliable default values */
2377 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2379 /* ----------------------------------------------------------------------- */
2380 /* ~/.<program>/levelsetup.conf */
2381 /* ----------------------------------------------------------------------- */
2383 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2385 if ((level_setup_hash = loadSetupFileHash(filename)))
2387 char *last_level_series =
2388 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2390 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2392 if (leveldir_current == NULL)
2393 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2395 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2397 freeSetupFileHash(level_setup_hash);
2400 Error(ERR_WARN, "using default setup values");
2405 void SaveLevelSetup_LastSeries()
2408 char *level_subdir = leveldir_current->filename;
2411 /* ----------------------------------------------------------------------- */
2412 /* ~/.<program>/levelsetup.conf */
2413 /* ----------------------------------------------------------------------- */
2415 InitUserDataDirectory();
2417 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2419 if (!(file = fopen(filename, MODE_WRITE)))
2421 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2426 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2427 getCookie("LEVELSETUP")));
2428 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2434 SetFilePermissions(filename, PERMS_PRIVATE);
2437 static void checkSeriesInfo()
2439 static char *level_directory = NULL;
2441 struct dirent *dir_entry;
2443 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2445 level_directory = getPath2((leveldir_current->user_defined ?
2446 getUserLevelDir(NULL) :
2447 options.level_directory),
2448 leveldir_current->fullpath);
2450 if ((dir = opendir(level_directory)) == NULL)
2452 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2456 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
2458 if (strlen(dir_entry->d_name) > 4 &&
2459 dir_entry->d_name[3] == '.' &&
2460 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2462 char levelnum_str[4];
2465 strncpy(levelnum_str, dir_entry->d_name, 3);
2466 levelnum_str[3] = '\0';
2468 levelnum_value = atoi(levelnum_str);
2471 if (levelnum_value < leveldir_current->first_level)
2473 Error(ERR_WARN, "additional level %d found", levelnum_value);
2474 leveldir_current->first_level = levelnum_value;
2476 else if (levelnum_value > leveldir_current->last_level)
2478 Error(ERR_WARN, "additional level %d found", levelnum_value);
2479 leveldir_current->last_level = levelnum_value;
2488 void LoadLevelSetup_SeriesInfo()
2491 SetupFileHash *level_setup_hash = NULL;
2492 char *level_subdir = leveldir_current->filename;
2494 /* always start with reliable default values */
2495 level_nr = leveldir_current->first_level;
2497 checkSeriesInfo(leveldir_current);
2499 /* ----------------------------------------------------------------------- */
2500 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2501 /* ----------------------------------------------------------------------- */
2503 level_subdir = leveldir_current->filename;
2505 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2507 if ((level_setup_hash = loadSetupFileHash(filename)))
2511 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2515 level_nr = atoi(token_value);
2517 if (level_nr < leveldir_current->first_level)
2518 level_nr = leveldir_current->first_level;
2519 if (level_nr > leveldir_current->last_level)
2520 level_nr = leveldir_current->last_level;
2523 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2527 int level_nr = atoi(token_value);
2529 if (level_nr < leveldir_current->first_level)
2530 level_nr = leveldir_current->first_level;
2531 if (level_nr > leveldir_current->last_level + 1)
2532 level_nr = leveldir_current->last_level;
2534 if (leveldir_current->user_defined)
2535 level_nr = leveldir_current->last_level;
2537 leveldir_current->handicap_level = level_nr;
2540 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2542 freeSetupFileHash(level_setup_hash);
2545 Error(ERR_WARN, "using default setup values");
2550 void SaveLevelSetup_SeriesInfo()
2553 char *level_subdir = leveldir_current->filename;
2554 char *level_nr_str = int2str(level_nr, 0);
2555 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2558 /* ----------------------------------------------------------------------- */
2559 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2560 /* ----------------------------------------------------------------------- */
2562 InitLevelSetupDirectory(level_subdir);
2564 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2566 if (!(file = fopen(filename, MODE_WRITE)))
2568 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2573 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2574 getCookie("LEVELSETUP")));
2575 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2577 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2578 handicap_level_str));
2583 SetFilePermissions(filename, PERMS_PRIVATE);