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 *getUserGraphicsDir()
273 static char *usergraphics_dir = NULL;
275 if (usergraphics_dir == NULL)
276 usergraphics_dir = getPath2(getUserDataDir(), GRAPHICS_DIRECTORY);
278 return usergraphics_dir;
281 static char *getUserSoundsDir()
283 static char *usersounds_dir = NULL;
285 if (usersounds_dir == NULL)
286 usersounds_dir = getPath2(getUserDataDir(), SOUNDS_DIRECTORY);
288 return usersounds_dir;
291 static char *getUserMusicDir()
293 static char *usermusic_dir = NULL;
295 if (usermusic_dir == NULL)
296 usermusic_dir = getPath2(getUserDataDir(), MUSIC_DIRECTORY);
298 return usermusic_dir;
301 static char *getSetupArtworkDir(TreeInfo *ti)
303 static char *artwork_dir = NULL;
305 if (artwork_dir != NULL)
308 artwork_dir = getPath2(ti->basepath, ti->fullpath);
313 void setLevelArtworkDir(TreeInfo *ti)
315 char **artwork_path_ptr, *artwork_set;
316 TreeInfo *level_artwork;
318 if (ti == NULL || leveldir_current == NULL)
322 (ti->type == TREE_TYPE_GRAPHICS_DIR ? &leveldir_current->graphics_path :
323 ti->type == TREE_TYPE_SOUNDS_DIR ? &leveldir_current->sounds_path :
324 &leveldir_current->music_path);
327 (ti->type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_set :
328 ti->type == TREE_TYPE_SOUNDS_DIR ? leveldir_current->sounds_set :
329 leveldir_current->music_set);
331 if ((level_artwork = getTreeInfoFromIdentifier(ti, artwork_set)) == NULL)
334 if (*artwork_path_ptr != NULL)
335 free(*artwork_path_ptr);
337 *artwork_path_ptr = getStringCopy(getSetupArtworkDir(level_artwork));
340 static char *getLevelArtworkDir(int type)
344 if (leveldir_current == NULL)
345 return UNDEFINED_FILENAME;
348 (type == TREE_TYPE_GRAPHICS_DIR ? leveldir_current->graphics_path :
349 type == TREE_TYPE_SOUNDS_DIR ? leveldir_current->sounds_path :
350 type == TREE_TYPE_MUSIC_DIR ? leveldir_current->music_path :
356 char *getLevelFilename(int nr)
358 static char *filename = NULL;
359 char basename[MAX_FILENAME_LEN];
361 if (filename != NULL)
365 sprintf(basename, "template.%s", LEVELFILE_EXTENSION);
367 sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
369 filename = getPath2(getCurrentLevelDir(), basename);
374 char *getTapeFilename(int nr)
376 static char *filename = NULL;
377 char basename[MAX_FILENAME_LEN];
379 if (filename != NULL)
382 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
383 filename = getPath2(getTapeDir(leveldir_current->filename), basename);
388 char *getScoreFilename(int nr)
390 static char *filename = NULL;
391 char basename[MAX_FILENAME_LEN];
393 if (filename != NULL)
396 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
397 filename = getPath2(getScoreDir(leveldir_current->filename), basename);
402 char *getSetupFilename()
404 static char *filename = NULL;
406 if (filename != NULL)
409 filename = getPath2(getSetupDir(), SETUP_FILENAME);
414 static char *getCorrectedImageBasename(char *basename)
416 char *basename_corrected = basename;
418 #if defined(PLATFORM_MSDOS)
419 if (program.filename_prefix != NULL)
421 int prefix_len = strlen(program.filename_prefix);
423 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
424 basename_corrected = &basename[prefix_len];
426 /* if corrected filename is still longer than standard MS-DOS filename
427 size (8 characters + 1 dot + 3 characters file extension), shorten
428 filename by writing file extension after 8th basename character */
429 if (strlen(basename_corrected) > 8+1+3)
431 static char *msdos_filename = NULL;
433 if (msdos_filename != NULL)
434 free(msdos_filename);
436 msdos_filename = getStringCopy(basename_corrected);
437 strncpy(&msdos_filename[8], &basename[strlen(basename) - 1+3], 1+3 + 1);
442 return basename_corrected;
445 char *getCustomImageFilename(char *basename)
447 static char *filename = NULL;
449 if (filename != NULL)
452 basename = getCorrectedImageBasename(basename);
454 if (!setup.override_level_graphics)
456 /* 1st try: look for special artwork configured in level series config */
457 filename = getPath2(getLevelArtworkDir(TREE_TYPE_GRAPHICS_DIR), basename);
458 if (fileExists(filename))
463 /* 2nd try: look for special artwork in current level series directory */
464 filename = getPath3(getCurrentLevelDir(), GRAPHICS_DIRECTORY, basename);
465 if (fileExists(filename))
471 /* 3rd try: look for special artwork in configured artwork directory */
472 filename = getPath2(getSetupArtworkDir(artwork.gfx_current), basename);
473 if (fileExists(filename))
478 /* 4th try: look for default artwork in new default artwork directory */
479 filename = getPath2(getDefaultGraphicsDir(GRAPHICS_SUBDIR), basename);
480 if (fileExists(filename))
485 /* 5th try: look for default artwork in old default artwork directory */
486 filename = getPath2(options.graphics_directory, basename);
487 if (fileExists(filename))
490 return NULL; /* cannot find specified artwork file anywhere */
493 char *getCustomSoundFilename(char *basename)
495 static char *filename = NULL;
497 if (filename != NULL)
500 if (!setup.override_level_sounds)
502 /* 1st try: look for special artwork configured in level series config */
503 filename = getPath2(getLevelArtworkDir(TREE_TYPE_SOUNDS_DIR), basename);
504 if (fileExists(filename))
509 /* 2nd try: look for special artwork in current level series directory */
510 filename = getPath3(getCurrentLevelDir(), SOUNDS_DIRECTORY, basename);
511 if (fileExists(filename))
517 /* 3rd try: look for special artwork in configured artwork directory */
518 filename = getPath2(getSetupArtworkDir(artwork.snd_current), basename);
519 if (fileExists(filename))
524 /* 4th try: look for default artwork in new default artwork directory */
525 filename = getPath2(getDefaultSoundsDir(SOUNDS_SUBDIR), basename);
526 if (fileExists(filename))
531 /* 5th try: look for default artwork in old default artwork directory */
532 filename = getPath2(options.sounds_directory, basename);
533 if (fileExists(filename))
536 return NULL; /* cannot find specified artwork file anywhere */
539 char *getCustomArtworkFilename(char *basename, int type)
541 if (type == ARTWORK_TYPE_GRAPHICS)
542 return getCustomImageFilename(basename);
543 else if (type == ARTWORK_TYPE_SOUNDS)
544 return getCustomSoundFilename(basename);
546 return UNDEFINED_FILENAME;
549 char *getCustomArtworkConfigFilename(int type)
551 return getCustomArtworkFilename(ARTWORKINFO_FILENAME(type), type);
554 char *getCustomMusicDirectory(void)
556 static char *directory = NULL;
558 if (directory != NULL)
561 if (!setup.override_level_music)
563 /* 1st try: look for special artwork configured in level series config */
564 directory = getStringCopy(getLevelArtworkDir(TREE_TYPE_MUSIC_DIR));
565 if (fileExists(directory))
570 /* 2nd try: look for special artwork in current level series directory */
571 directory = getPath2(getCurrentLevelDir(), MUSIC_DIRECTORY);
572 if (fileExists(directory))
578 /* 3rd try: look for special artwork in configured artwork directory */
579 directory = getStringCopy(getSetupArtworkDir(artwork.mus_current));
580 if (fileExists(directory))
585 /* 4th try: look for default artwork in new default artwork directory */
586 directory = getStringCopy(getDefaultMusicDir(MUSIC_SUBDIR));
587 if (fileExists(directory))
592 /* 5th try: look for default artwork in old default artwork directory */
593 directory = getStringCopy(options.music_directory);
594 if (fileExists(directory))
597 return NULL; /* cannot find specified artwork file anywhere */
600 void InitTapeDirectory(char *level_subdir)
602 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
603 createDirectory(getTapeDir(NULL), "main tape", PERMS_PRIVATE);
604 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
607 void InitScoreDirectory(char *level_subdir)
609 createDirectory(getCommonDataDir(), "common data", PERMS_PUBLIC);
610 createDirectory(getScoreDir(NULL), "main score", PERMS_PUBLIC);
611 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
614 static void SaveUserLevelInfo();
616 void InitUserLevelDirectory(char *level_subdir)
618 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
620 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
621 createDirectory(getUserLevelDir(NULL), "main user level", PERMS_PRIVATE);
622 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
628 void InitLevelSetupDirectory(char *level_subdir)
630 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
631 createDirectory(getLevelSetupDir(NULL), "main level setup", PERMS_PRIVATE);
632 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
636 /* ------------------------------------------------------------------------- */
637 /* some functions to handle lists of level directories */
638 /* ------------------------------------------------------------------------- */
640 TreeInfo *newTreeInfo()
642 return checked_calloc(sizeof(TreeInfo));
645 void pushTreeInfo(TreeInfo **node_first, TreeInfo *node_new)
647 node_new->next = *node_first;
648 *node_first = node_new;
651 int numTreeInfo(TreeInfo *node)
664 boolean validLevelSeries(TreeInfo *node)
666 return (node != NULL && !node->node_group && !node->parent_link);
669 TreeInfo *getFirstValidTreeInfoEntry(TreeInfo *node)
674 if (node->node_group) /* enter level group (step down into tree) */
675 return getFirstValidTreeInfoEntry(node->node_group);
676 else if (node->parent_link) /* skip start entry of level group */
678 if (node->next) /* get first real level series entry */
679 return getFirstValidTreeInfoEntry(node->next);
680 else /* leave empty level group and go on */
681 return getFirstValidTreeInfoEntry(node->node_parent->next);
683 else /* this seems to be a regular level series */
687 TreeInfo *getTreeInfoFirstGroupEntry(TreeInfo *node)
692 if (node->node_parent == NULL) /* top level group */
693 return *node->node_top;
694 else /* sub level group */
695 return node->node_parent->node_group;
698 int numTreeInfoInGroup(TreeInfo *node)
700 return numTreeInfo(getTreeInfoFirstGroupEntry(node));
703 int posTreeInfo(TreeInfo *node)
705 TreeInfo *node_cmp = getTreeInfoFirstGroupEntry(node);
710 if (node_cmp == node)
714 node_cmp = node_cmp->next;
720 TreeInfo *getTreeInfoFromPos(TreeInfo *node, int pos)
722 TreeInfo *node_default = node;
737 TreeInfo *getTreeInfoFromIdentifier(TreeInfo *node, char *identifier)
739 if (identifier == NULL)
744 if (node->node_group)
746 TreeInfo *node_group;
748 node_group = getTreeInfoFromIdentifier(node->node_group, identifier);
753 else if (!node->parent_link)
755 if (strcmp(identifier, node->identifier) == 0)
765 void dumpTreeInfo(TreeInfo *node, int depth)
769 printf("Dumping TreeInfo:\n");
773 for (i=0; i<(depth + 1) * 3; i++)
776 printf("filename == '%s' (%s) [%s] (%d)\n",
777 node->filename, node->name, node->identifier, node->sort_priority);
779 if (node->node_group != NULL)
780 dumpTreeInfo(node->node_group, depth + 1);
786 void sortTreeInfo(TreeInfo **node_first,
787 int (*compare_function)(const void *, const void *))
789 int num_nodes = numTreeInfo(*node_first);
790 TreeInfo **sort_array;
791 TreeInfo *node = *node_first;
797 /* allocate array for sorting structure pointers */
798 sort_array = checked_calloc(num_nodes * sizeof(TreeInfo *));
800 /* writing structure pointers to sorting array */
801 while (i < num_nodes && node) /* double boundary check... */
803 sort_array[i] = node;
809 /* sorting the structure pointers in the sorting array */
810 qsort(sort_array, num_nodes, sizeof(TreeInfo *),
813 /* update the linkage of list elements with the sorted node array */
814 for (i=0; i<num_nodes - 1; i++)
815 sort_array[i]->next = sort_array[i + 1];
816 sort_array[num_nodes - 1]->next = NULL;
818 /* update the linkage of the main list anchor pointer */
819 *node_first = sort_array[0];
823 /* now recursively sort the level group structures */
827 if (node->node_group != NULL)
828 sortTreeInfo(&node->node_group, compare_function);
835 /* ========================================================================= */
836 /* some stuff from "files.c" */
837 /* ========================================================================= */
839 #if defined(PLATFORM_WIN32)
841 #define S_IRGRP S_IRUSR
844 #define S_IROTH S_IRUSR
847 #define S_IWGRP S_IWUSR
850 #define S_IWOTH S_IWUSR
853 #define S_IXGRP S_IXUSR
856 #define S_IXOTH S_IXUSR
859 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
864 #endif /* PLATFORM_WIN32 */
866 /* file permissions for newly written files */
867 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
868 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
869 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
871 #define MODE_W_PRIVATE (S_IWUSR)
872 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
873 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
875 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
876 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
878 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
879 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
881 char *getUserDataDir(void)
883 static char *userdata_dir = NULL;
885 if (userdata_dir == NULL)
886 userdata_dir = getPath2(getHomeDir(), program.userdata_directory);
891 char *getCommonDataDir(void)
893 static char *common_data_dir = NULL;
895 #if defined(PLATFORM_WIN32)
896 if (common_data_dir == NULL)
898 char *dir = checked_malloc(MAX_PATH + 1);
900 if (SUCCEEDED(SHGetFolderPath(NULL, CSIDL_COMMON_DOCUMENTS, NULL, 0, dir))
901 && strcmp(dir, "") != 0) /* empty for Windows 95/98 */
902 common_data_dir = getPath2(dir, program.userdata_directory);
904 common_data_dir = options.rw_base_directory;
907 if (common_data_dir == NULL)
908 common_data_dir = options.rw_base_directory;
911 return common_data_dir;
916 return getUserDataDir();
919 static mode_t posix_umask(mode_t mask)
921 #if defined(PLATFORM_UNIX)
928 static int posix_mkdir(const char *pathname, mode_t mode)
930 #if defined(PLATFORM_WIN32)
931 return mkdir(pathname);
933 return mkdir(pathname, mode);
937 void createDirectory(char *dir, char *text, int permission_class)
939 /* leave "other" permissions in umask untouched, but ensure group parts
940 of USERDATA_DIR_MODE are not masked */
941 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
942 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
943 mode_t normal_umask = posix_umask(0);
944 mode_t group_umask = ~(dir_mode & S_IRWXG);
945 posix_umask(normal_umask & group_umask);
947 if (access(dir, F_OK) != 0)
948 if (posix_mkdir(dir, dir_mode) != 0)
949 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
951 posix_umask(normal_umask); /* reset normal umask */
954 void InitUserDataDirectory()
956 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
959 void SetFilePermissions(char *filename, int permission_class)
961 chmod(filename, (permission_class == PERMS_PRIVATE ?
962 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
965 char *getCookie(char *file_type)
967 static char cookie[MAX_COOKIE_LEN + 1];
969 if (strlen(program.cookie_prefix) + 1 +
970 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
971 return "[COOKIE ERROR]"; /* should never happen */
973 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
974 program.cookie_prefix, file_type,
975 program.version_major, program.version_minor);
980 int getFileVersionFromCookieString(const char *cookie)
982 const char *ptr_cookie1, *ptr_cookie2;
983 const char *pattern1 = "_FILE_VERSION_";
984 const char *pattern2 = "?.?";
985 const int len_cookie = strlen(cookie);
986 const int len_pattern1 = strlen(pattern1);
987 const int len_pattern2 = strlen(pattern2);
988 const int len_pattern = len_pattern1 + len_pattern2;
989 int version_major, version_minor;
991 if (len_cookie <= len_pattern)
994 ptr_cookie1 = &cookie[len_cookie - len_pattern];
995 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
997 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
1000 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
1001 ptr_cookie2[1] != '.' ||
1002 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
1005 version_major = ptr_cookie2[0] - '0';
1006 version_minor = ptr_cookie2[2] - '0';
1008 return VERSION_IDENT(version_major, version_minor, 0);
1011 boolean checkCookieString(const char *cookie, const char *template)
1013 const char *pattern = "_FILE_VERSION_?.?";
1014 const int len_cookie = strlen(cookie);
1015 const int len_template = strlen(template);
1016 const int len_pattern = strlen(pattern);
1018 if (len_cookie != len_template)
1021 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
1027 /* ------------------------------------------------------------------------- */
1028 /* setup file list and hash handling functions */
1029 /* ------------------------------------------------------------------------- */
1031 char *getFormattedSetupEntry(char *token, char *value)
1034 static char entry[MAX_LINE_LEN];
1036 /* start with the token and some spaces to format output line */
1037 sprintf(entry, "%s:", token);
1038 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1041 /* continue with the token's value */
1042 strcat(entry, value);
1047 SetupFileList *newSetupFileList(char *token, char *value)
1049 SetupFileList *new = checked_malloc(sizeof(SetupFileList));
1051 new->token = getStringCopy(token);
1052 new->value = getStringCopy(value);
1059 void freeSetupFileList(SetupFileList *list)
1069 freeSetupFileList(list->next);
1073 char *getListEntry(SetupFileList *list, char *token)
1078 if (strcmp(list->token, token) == 0)
1081 return getListEntry(list->next, token);
1084 void setListEntry(SetupFileList *list, char *token, char *value)
1089 if (strcmp(list->token, token) == 0)
1094 list->value = getStringCopy(value);
1096 else if (list->next == NULL)
1097 list->next = newSetupFileList(token, value);
1099 setListEntry(list->next, token, value);
1103 static void printSetupFileList(SetupFileList *list)
1108 printf("token: '%s'\n", list->token);
1109 printf("value: '%s'\n", list->value);
1111 printSetupFileList(list->next);
1116 DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char);
1117 DEFINE_HASHTABLE_SEARCH(search_hash_entry, char, char);
1118 DEFINE_HASHTABLE_CHANGE(change_hash_entry, char, char);
1119 DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char);
1121 #define insert_hash_entry hashtable_insert
1122 #define search_hash_entry hashtable_search
1123 #define change_hash_entry hashtable_change
1124 #define remove_hash_entry hashtable_remove
1127 static unsigned int get_hash_from_key(void *key)
1132 This algorithm (k=33) was first reported by Dan Bernstein many years ago in
1133 'comp.lang.c'. Another version of this algorithm (now favored by Bernstein)
1134 uses XOR: hash(i) = hash(i - 1) * 33 ^ str[i]; the magic of number 33 (why
1135 it works better than many other constants, prime or not) has never been
1136 adequately explained.
1138 If you just want to have a good hash function, and cannot wait, djb2
1139 is one of the best string hash functions i know. It has excellent
1140 distribution and speed on many different sets of keys and table sizes.
1141 You are not likely to do better with one of the "well known" functions
1142 such as PJW, K&R, etc.
1144 Ozan (oz) Yigit [http://www.cs.yorku.ca/~oz/hash.html]
1147 char *str = (char *)key;
1148 unsigned int hash = 5381;
1151 while ((c = *str++))
1152 hash = ((hash << 5) + hash) + c; /* hash * 33 + c */
1157 static int keys_are_equal(void *key1, void *key2)
1159 return (strcmp((char *)key1, (char *)key2) == 0);
1162 SetupFileHash *newSetupFileHash()
1164 SetupFileHash *new_hash =
1165 create_hashtable(16, 0.75, get_hash_from_key, keys_are_equal);
1170 void freeSetupFileHash(SetupFileHash *hash)
1175 hashtable_destroy(hash, 1); /* 1 == also free values stored in hash */
1178 char *getHashEntry(SetupFileHash *hash, char *token)
1183 return search_hash_entry(hash, token);
1186 void setHashEntry(SetupFileHash *hash, char *token, char *value)
1193 value_copy = getStringCopy(value);
1195 /* change value; if it does not exist, insert it as new */
1196 if (!change_hash_entry(hash, token, value_copy))
1197 if (!insert_hash_entry(hash, getStringCopy(token), value_copy))
1198 Error(ERR_EXIT, "cannot insert into hash -- aborting");
1203 static void printSetupFileHash(SetupFileHash *hash)
1205 BEGIN_HASH_ITERATION(hash, itr)
1207 printf("token: '%s'\n", HASH_ITERATION_TOKEN(itr));
1208 printf("value: '%s'\n", HASH_ITERATION_VALUE(itr));
1210 END_HASH_ITERATION(hash, itr)
1215 static void *loadSetupFileData(char *filename, boolean use_hash)
1218 char line[MAX_LINE_LEN];
1219 char *token, *value, *line_ptr;
1220 void *setup_file_data;
1224 setup_file_data = newSetupFileHash();
1226 setup_file_data = newSetupFileList("", "");
1228 if (!(file = fopen(filename, MODE_READ)))
1230 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
1236 /* read next line of input file */
1237 if (!fgets(line, MAX_LINE_LEN, file))
1240 /* cut trailing comment or whitespace from input line */
1241 for (line_ptr = line; *line_ptr; line_ptr++)
1243 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
1250 /* cut trailing whitespaces from input line */
1251 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
1252 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
1255 /* ignore empty lines */
1259 line_len = strlen(line);
1261 /* cut leading whitespaces from token */
1262 for (token = line; *token; token++)
1263 if (*token != ' ' && *token != '\t')
1266 /* find end of token */
1267 for (line_ptr = token; *line_ptr; line_ptr++)
1269 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
1276 if (line_ptr < line + line_len)
1277 value = line_ptr + 1;
1281 /* cut leading whitespaces from value */
1282 for (; *value; value++)
1283 if (*value != ' ' && *value != '\t')
1286 if (*token && *value)
1289 setHashEntry((SetupFileHash *)setup_file_data, token, value);
1291 setListEntry((SetupFileList *)setup_file_data, token, value);
1299 if (hashtable_count((SetupFileHash *)setup_file_data) == 0)
1300 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1304 SetupFileList *setup_file_list = (SetupFileList *)setup_file_data;
1305 SetupFileList *first_valid_list_entry = setup_file_list->next;
1307 /* free empty list header */
1308 setup_file_list->next = NULL;
1309 freeSetupFileList(setup_file_list);
1310 setup_file_data = first_valid_list_entry;
1312 if (first_valid_list_entry == NULL)
1313 Error(ERR_WARN, "configuration file '%s' is empty", filename);
1316 return setup_file_data;
1319 SetupFileList *loadSetupFileList(char *filename)
1321 return (SetupFileList *)loadSetupFileData(filename, FALSE);
1324 SetupFileHash *loadSetupFileHash(char *filename)
1326 return (SetupFileHash *)loadSetupFileData(filename, TRUE);
1329 void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash,
1332 char *value = getHashEntry(setup_file_hash, TOKEN_STR_FILE_IDENTIFIER);
1335 Error(ERR_WARN, "configuration file has no file identifier");
1336 else if (!checkCookieString(value, identifier))
1337 Error(ERR_WARN, "configuration file has wrong file identifier");
1341 /* ========================================================================= */
1342 /* setup file stuff */
1343 /* ========================================================================= */
1345 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
1346 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
1347 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
1349 /* level directory info */
1350 #define LEVELINFO_TOKEN_IDENTIFIER 0
1351 #define LEVELINFO_TOKEN_NAME 1
1352 #define LEVELINFO_TOKEN_NAME_SORTING 2
1353 #define LEVELINFO_TOKEN_AUTHOR 3
1354 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
1355 #define LEVELINFO_TOKEN_LEVELS 5
1356 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
1357 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
1358 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
1359 #define LEVELINFO_TOKEN_READONLY 9
1360 #define LEVELINFO_TOKEN_GRAPHICS_SET 10
1361 #define LEVELINFO_TOKEN_SOUNDS_SET 11
1362 #define LEVELINFO_TOKEN_MUSIC_SET 12
1364 #define NUM_LEVELINFO_TOKENS 13
1366 static LevelDirTree ldi;
1368 static struct TokenInfo levelinfo_tokens[] =
1370 /* level directory info */
1371 { TYPE_STRING, &ldi.identifier, "identifier" },
1372 { TYPE_STRING, &ldi.name, "name" },
1373 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1374 { TYPE_STRING, &ldi.author, "author" },
1375 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1376 { TYPE_INTEGER, &ldi.levels, "levels" },
1377 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1378 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1379 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1380 { TYPE_BOOLEAN, &ldi.readonly, "readonly" },
1381 { TYPE_STRING, &ldi.graphics_set, "graphics_set" },
1382 { TYPE_STRING, &ldi.sounds_set, "sounds_set" },
1383 { TYPE_STRING, &ldi.music_set, "music_set" }
1386 static void setTreeInfoToDefaults(TreeInfo *ldi, int type)
1390 ldi->node_top = (ldi->type == TREE_TYPE_LEVEL_DIR ? &leveldir_first :
1391 ldi->type == TREE_TYPE_GRAPHICS_DIR ? &artwork.gfx_first :
1392 ldi->type == TREE_TYPE_SOUNDS_DIR ? &artwork.snd_first :
1393 ldi->type == TREE_TYPE_MUSIC_DIR ? &artwork.mus_first :
1396 ldi->node_parent = NULL;
1397 ldi->node_group = NULL;
1401 ldi->cl_cursor = -1;
1403 ldi->filename = NULL;
1404 ldi->fullpath = NULL;
1405 ldi->basepath = NULL;
1406 ldi->identifier = NULL;
1407 ldi->name = getStringCopy(ANONYMOUS_NAME);
1408 ldi->name_sorting = NULL;
1409 ldi->author = getStringCopy(ANONYMOUS_NAME);
1411 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1412 ldi->parent_link = FALSE;
1413 ldi->user_defined = FALSE;
1415 ldi->class_desc = NULL;
1417 if (ldi->type == TREE_TYPE_LEVEL_DIR)
1419 ldi->imported_from = NULL;
1420 ldi->graphics_set = NULL;
1421 ldi->sounds_set = NULL;
1422 ldi->music_set = NULL;
1423 ldi->graphics_path = getStringCopy(UNDEFINED_FILENAME);
1424 ldi->sounds_path = getStringCopy(UNDEFINED_FILENAME);
1425 ldi->music_path = getStringCopy(UNDEFINED_FILENAME);
1427 ldi->first_level = 0;
1428 ldi->last_level = 0;
1429 ldi->level_group = FALSE;
1430 ldi->handicap_level = 0;
1431 ldi->readonly = TRUE;
1435 static void setTreeInfoToDefaultsFromParent(TreeInfo *ldi, TreeInfo *parent)
1439 Error(ERR_WARN, "setTreeInfoToDefaultsFromParent(): parent == NULL");
1441 setTreeInfoToDefaults(ldi, TREE_TYPE_GENERIC);
1445 /* first copy all values from the parent structure ... */
1448 /* ... then set all fields to default that cannot be inherited from parent.
1449 This is especially important for all those fields that can be set from
1450 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1451 calls 'free()' for all already set token values which requires that no
1452 other structure's pointer may point to them!
1455 ldi->filename = NULL;
1456 ldi->fullpath = NULL;
1457 ldi->basepath = NULL;
1458 ldi->identifier = NULL;
1459 ldi->name = getStringCopy(ANONYMOUS_NAME);
1460 ldi->name_sorting = NULL;
1461 ldi->author = getStringCopy(parent->author);
1462 ldi->imported_from = getStringCopy(parent->imported_from);
1464 ldi->level_group = FALSE;
1465 ldi->parent_link = FALSE;
1467 ldi->node_top = parent->node_top;
1468 ldi->node_parent = parent;
1469 ldi->node_group = NULL;
1473 void setSetupInfo(struct TokenInfo *token_info,
1474 int token_nr, char *token_value)
1476 int token_type = token_info[token_nr].type;
1477 void *setup_value = token_info[token_nr].value;
1479 if (token_value == NULL)
1482 /* set setup field to corresponding token value */
1487 *(boolean *)setup_value = get_boolean_from_string(token_value);
1491 *(Key *)setup_value = getKeyFromKeyName(token_value);
1495 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1499 *(int *)setup_value = get_integer_from_string(token_value);
1503 if (*(char **)setup_value != NULL)
1504 free(*(char **)setup_value);
1505 *(char **)setup_value = getStringCopy(token_value);
1513 static int compareTreeInfoEntries(const void *object1, const void *object2)
1515 const TreeInfo *entry1 = *((TreeInfo **)object1);
1516 const TreeInfo *entry2 = *((TreeInfo **)object2);
1517 int class_sorting1, class_sorting2;
1520 if (entry1->type == TREE_TYPE_LEVEL_DIR)
1522 class_sorting1 = LEVELSORTING(entry1);
1523 class_sorting2 = LEVELSORTING(entry2);
1527 class_sorting1 = ARTWORKSORTING(entry1);
1528 class_sorting2 = ARTWORKSORTING(entry2);
1531 if (entry1->parent_link || entry2->parent_link)
1532 compare_result = (entry1->parent_link ? -1 : +1);
1533 else if (entry1->sort_priority == entry2->sort_priority)
1535 char *name1 = getStringToLower(entry1->name_sorting);
1536 char *name2 = getStringToLower(entry2->name_sorting);
1538 compare_result = strcmp(name1, name2);
1543 else if (class_sorting1 == class_sorting2)
1544 compare_result = entry1->sort_priority - entry2->sort_priority;
1546 compare_result = class_sorting1 - class_sorting2;
1548 return compare_result;
1551 static void createParentTreeInfoNode(TreeInfo *node_parent)
1555 if (node_parent == NULL)
1558 ti_new = newTreeInfo();
1559 setTreeInfoToDefaults(ti_new, node_parent->type);
1561 ti_new->node_parent = node_parent;
1562 ti_new->parent_link = TRUE;
1564 ti_new->identifier = getStringCopy(node_parent->identifier);
1565 ti_new->name = ".. (parent directory)";
1566 ti_new->name_sorting = getStringCopy(ti_new->name);
1568 ti_new->filename = "..";
1569 ti_new->fullpath = getStringCopy(node_parent->fullpath);
1571 ti_new->sort_priority = node_parent->sort_priority;
1572 ti_new->class_desc = getLevelClassDescription(ti_new);
1574 pushTreeInfo(&node_parent->node_group, ti_new);
1577 /* forward declaration for recursive call by "LoadLevelInfoFromLevelDir()" */
1578 static void LoadLevelInfoFromLevelDir(TreeInfo **, TreeInfo *, char *);
1580 static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first,
1581 TreeInfo *node_parent,
1582 char *level_directory,
1583 char *directory_name)
1585 char *directory_path = getPath2(level_directory, directory_name);
1586 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1587 SetupFileHash *setup_file_hash = loadSetupFileHash(filename);
1588 LevelDirTree *leveldir_new = NULL;
1591 if (setup_file_hash == NULL)
1593 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1595 free(directory_path);
1601 leveldir_new = newTreeInfo();
1604 setTreeInfoToDefaultsFromParent(leveldir_new, node_parent);
1606 setTreeInfoToDefaults(leveldir_new, TREE_TYPE_LEVEL_DIR);
1608 leveldir_new->filename = getStringCopy(directory_name);
1610 checkSetupFileHashIdentifier(setup_file_hash, getCookie("LEVELINFO"));
1612 /* set all structure fields according to the token/value pairs */
1613 ldi = *leveldir_new;
1614 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1615 setSetupInfo(levelinfo_tokens, i,
1616 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1617 *leveldir_new = ldi;
1619 if (strcmp(leveldir_new->name, ANONYMOUS_NAME) == 0)
1621 free(leveldir_new->name);
1622 leveldir_new->name = getStringCopy(leveldir_new->filename);
1625 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1627 if (leveldir_new->identifier == NULL)
1628 leveldir_new->identifier = getStringCopy(leveldir_new->filename);
1630 if (leveldir_new->name_sorting == NULL)
1631 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1633 if (node_parent == NULL) /* top level group */
1635 leveldir_new->basepath = level_directory;
1636 leveldir_new->fullpath = leveldir_new->filename;
1638 else /* sub level group */
1640 leveldir_new->basepath = node_parent->basepath;
1641 leveldir_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1644 if (leveldir_new->levels < 1)
1645 leveldir_new->levels = 1;
1647 leveldir_new->last_level =
1648 leveldir_new->first_level + leveldir_new->levels - 1;
1650 leveldir_new->user_defined =
1651 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1653 leveldir_new->color = LEVELCOLOR(leveldir_new);
1654 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1656 leveldir_new->handicap_level = /* set handicap to default value */
1657 (leveldir_new->user_defined ?
1658 leveldir_new->last_level :
1659 leveldir_new->first_level);
1661 pushTreeInfo(node_first, leveldir_new);
1663 freeSetupFileHash(setup_file_hash);
1665 if (leveldir_new->level_group)
1667 /* create node to link back to current level directory */
1668 createParentTreeInfoNode(leveldir_new);
1670 /* step into sub-directory and look for more level series */
1671 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1672 leveldir_new, directory_path);
1675 free(directory_path);
1681 static void LoadLevelInfoFromLevelDir(TreeInfo **node_first,
1682 TreeInfo *node_parent,
1683 char *level_directory)
1686 struct dirent *dir_entry;
1687 boolean valid_entry_found = FALSE;
1689 if ((dir = opendir(level_directory)) == NULL)
1691 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1695 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1697 struct stat file_status;
1698 char *directory_name = dir_entry->d_name;
1699 char *directory_path = getPath2(level_directory, directory_name);
1701 /* skip entries for current and parent directory */
1702 if (strcmp(directory_name, ".") == 0 ||
1703 strcmp(directory_name, "..") == 0)
1705 free(directory_path);
1709 /* find out if directory entry is itself a directory */
1710 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1711 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1713 free(directory_path);
1717 free(directory_path);
1719 if (strcmp(directory_name, GRAPHICS_DIRECTORY) == 0 ||
1720 strcmp(directory_name, SOUNDS_DIRECTORY) == 0 ||
1721 strcmp(directory_name, MUSIC_DIRECTORY) == 0)
1724 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1731 if (!valid_entry_found)
1733 /* check if this directory directly contains a file "levelinfo.conf" */
1734 valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent,
1735 level_directory, ".");
1738 if (!valid_entry_found)
1739 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1743 void LoadLevelInfo()
1745 InitUserLevelDirectory(getLoginName());
1747 DrawInitText("Loading level series:", 120, FC_GREEN);
1749 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1750 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(NULL));
1752 /* before sorting, the first entries will be from the user directory */
1753 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
1755 if (leveldir_first == NULL)
1756 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1758 sortTreeInfo(&leveldir_first, compareTreeInfoEntries);
1761 dumpTreeInfo(leveldir_first, 0);
1765 static boolean LoadArtworkInfoFromArtworkConf(TreeInfo **node_first,
1766 TreeInfo *node_parent,
1767 char *base_directory,
1768 char *directory_name, int type)
1770 char *directory_path = getPath2(base_directory, directory_name);
1771 char *filename = getPath2(directory_path, ARTWORKINFO_FILENAME(type));
1772 SetupFileHash *setup_file_hash = NULL;
1773 TreeInfo *artwork_new = NULL;
1776 if (access(filename, F_OK) == 0) /* file exists */
1777 setup_file_hash = loadSetupFileHash(filename);
1779 if (setup_file_hash == NULL) /* no config file -- look for artwork files */
1782 struct dirent *dir_entry;
1783 boolean valid_file_found = FALSE;
1785 if ((dir = opendir(directory_path)) != NULL)
1787 while ((dir_entry = readdir(dir)) != NULL)
1789 char *entry_name = dir_entry->d_name;
1791 if (FileIsArtworkType(entry_name, type))
1793 valid_file_found = TRUE;
1801 if (!valid_file_found)
1803 if (strcmp(directory_name, ".") != 0)
1804 Error(ERR_WARN, "ignoring artwork directory '%s'", directory_path);
1806 free(directory_path);
1813 artwork_new = newTreeInfo();
1816 setTreeInfoToDefaultsFromParent(artwork_new, node_parent);
1818 setTreeInfoToDefaults(artwork_new, type);
1820 artwork_new->filename = getStringCopy(directory_name);
1822 if (setup_file_hash) /* (before defining ".color" and ".class_desc") */
1825 checkSetupFileHashIdentifier(setup_file_hash, getCookie("..."));
1828 /* set all structure fields according to the token/value pairs */
1830 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1831 setSetupInfo(levelinfo_tokens, i,
1832 getHashEntry(setup_file_hash, levelinfo_tokens[i].text));
1835 if (strcmp(artwork_new->name, ANONYMOUS_NAME) == 0)
1837 free(artwork_new->name);
1838 artwork_new->name = getStringCopy(artwork_new->filename);
1842 DrawInitText(artwork_new->name, 150, FC_YELLOW);
1845 if (artwork_new->identifier == NULL)
1846 artwork_new->identifier = getStringCopy(artwork_new->filename);
1848 if (artwork_new->name_sorting == NULL)
1849 artwork_new->name_sorting = getStringCopy(artwork_new->name);
1852 if (node_parent == NULL) /* top level group */
1854 artwork_new->basepath = getStringCopy(base_directory);
1855 artwork_new->fullpath = getStringCopy(artwork_new->filename);
1857 else /* sub level group */
1859 artwork_new->basepath = getStringCopy(node_parent->basepath);
1860 artwork_new->fullpath = getPath2(node_parent->fullpath, directory_name);
1863 artwork_new->user_defined =
1864 (artwork_new->basepath == OPTIONS_ARTWORK_DIRECTORY(type) ? FALSE : TRUE);
1866 /* (may use ".sort_priority" from "setup_file_hash" above) */
1867 artwork_new->color = ARTWORKCOLOR(artwork_new);
1868 artwork_new->class_desc = getLevelClassDescription(artwork_new);
1870 if (setup_file_hash == NULL) /* (after determining ".user_defined") */
1872 if (artwork_new->name != NULL)
1873 free(artwork_new->name);
1875 if (strcmp(artwork_new->filename, ".") == 0)
1877 if (artwork_new->user_defined)
1879 artwork_new->identifier = getStringCopy("private");
1880 artwork_new->sort_priority = ARTWORKCLASS_USER;
1884 artwork_new->identifier = getStringCopy("classic");
1885 artwork_new->sort_priority = ARTWORKCLASS_CLASSICS;
1888 /* set to new values after changing ".sort_priority" */
1889 artwork_new->color = ARTWORKCOLOR(artwork_new);
1890 artwork_new->class_desc = getLevelClassDescription(artwork_new);
1894 artwork_new->identifier = getStringCopy(artwork_new->filename);
1897 artwork_new->name = getStringCopy(artwork_new->identifier);
1898 artwork_new->name_sorting = getStringCopy(artwork_new->name);
1901 DrawInitText(artwork_new->name, 150, FC_YELLOW);
1903 pushTreeInfo(node_first, artwork_new);
1905 freeSetupFileHash(setup_file_hash);
1907 free(directory_path);
1913 static void LoadArtworkInfoFromArtworkDir(TreeInfo **node_first,
1914 TreeInfo *node_parent,
1915 char *base_directory, int type)
1918 struct dirent *dir_entry;
1919 boolean valid_entry_found = FALSE;
1921 if ((dir = opendir(base_directory)) == NULL)
1923 if (base_directory == OPTIONS_ARTWORK_DIRECTORY(type))
1924 Error(ERR_WARN, "cannot read directory '%s'", base_directory);
1928 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1930 struct stat file_status;
1931 char *directory_name = dir_entry->d_name;
1932 char *directory_path = getPath2(base_directory, directory_name);
1934 /* skip entries for current and parent directory */
1935 if (strcmp(directory_name, ".") == 0 ||
1936 strcmp(directory_name, "..") == 0)
1938 free(directory_path);
1942 /* find out if directory entry is itself a directory */
1943 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1944 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1946 free(directory_path);
1950 free(directory_path);
1952 /* check if this directory contains artwork with or without config file */
1953 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1955 directory_name, type);
1960 /* check if this directory directly contains artwork itself */
1961 valid_entry_found |= LoadArtworkInfoFromArtworkConf(node_first,node_parent,
1962 base_directory, ".",
1964 if (!valid_entry_found)
1965 Error(ERR_WARN, "cannot find any valid artwork in directory '%s'",
1969 static TreeInfo *getDummyArtworkInfo(int type)
1971 /* this is only needed when there is completely no artwork available */
1972 TreeInfo *artwork_new = newTreeInfo();
1974 setTreeInfoToDefaults(artwork_new, type);
1976 artwork_new->filename = getStringCopy(UNDEFINED_FILENAME);
1977 artwork_new->fullpath = getStringCopy(UNDEFINED_FILENAME);
1978 artwork_new->basepath = getStringCopy(UNDEFINED_FILENAME);
1980 if (artwork_new->name != NULL)
1981 free(artwork_new->name);
1983 artwork_new->identifier = getStringCopy(UNDEFINED_FILENAME);
1984 artwork_new->name = getStringCopy(UNDEFINED_FILENAME);
1985 artwork_new->name_sorting = getStringCopy(UNDEFINED_FILENAME);
1990 void LoadArtworkInfo()
1992 DrawInitText("Looking for custom artwork:", 120, FC_GREEN);
1994 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1995 options.graphics_directory,
1996 TREE_TYPE_GRAPHICS_DIR);
1997 LoadArtworkInfoFromArtworkDir(&artwork.gfx_first, NULL,
1998 getUserGraphicsDir(),
1999 TREE_TYPE_GRAPHICS_DIR);
2001 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2002 options.sounds_directory,
2003 TREE_TYPE_SOUNDS_DIR);
2004 LoadArtworkInfoFromArtworkDir(&artwork.snd_first, NULL,
2006 TREE_TYPE_SOUNDS_DIR);
2008 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2009 options.music_directory,
2010 TREE_TYPE_MUSIC_DIR);
2011 LoadArtworkInfoFromArtworkDir(&artwork.mus_first, NULL,
2013 TREE_TYPE_MUSIC_DIR);
2015 if (artwork.gfx_first == NULL)
2016 artwork.gfx_first = getDummyArtworkInfo(TREE_TYPE_GRAPHICS_DIR);
2017 if (artwork.snd_first == NULL)
2018 artwork.snd_first = getDummyArtworkInfo(TREE_TYPE_SOUNDS_DIR);
2019 if (artwork.mus_first == NULL)
2020 artwork.mus_first = getDummyArtworkInfo(TREE_TYPE_MUSIC_DIR);
2022 /* before sorting, the first entries will be from the user directory */
2023 artwork.gfx_current =
2024 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2025 if (artwork.gfx_current == NULL)
2026 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2028 artwork.snd_current =
2029 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2030 if (artwork.snd_current == NULL)
2031 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2033 artwork.mus_current =
2034 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2035 if (artwork.mus_current == NULL)
2036 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2038 artwork.gfx_current_identifier = artwork.gfx_current->identifier;
2039 artwork.snd_current_identifier = artwork.snd_current->identifier;
2040 artwork.mus_current_identifier = artwork.mus_current->identifier;
2043 printf("graphics set == %s\n\n", artwork.gfx_current_identifier);
2044 printf("sounds set == %s\n\n", artwork.snd_current_identifier);
2045 printf("music set == %s\n\n", artwork.mus_current_identifier);
2048 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2049 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2050 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2053 dumpTreeInfo(artwork.gfx_first, 0);
2054 dumpTreeInfo(artwork.snd_first, 0);
2055 dumpTreeInfo(artwork.mus_first, 0);
2059 void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node,
2060 LevelDirTree *level_node)
2062 /* recursively check all level directories for artwork sub-directories */
2066 char *path = getPath2(getLevelDirFromTreeInfo(level_node),
2067 ARTWORK_DIRECTORY((*artwork_node)->type));
2070 if (!level_node->parent_link)
2071 printf("CHECKING '%s' ['%s', '%s'] ...\n", path,
2072 level_node->filename, level_node->name);
2075 if (!level_node->parent_link)
2077 TreeInfo *topnode_last = *artwork_node;
2079 LoadArtworkInfoFromArtworkDir(artwork_node, NULL, path,
2080 (*artwork_node)->type);
2082 if (topnode_last != *artwork_node)
2084 free((*artwork_node)->identifier);
2085 free((*artwork_node)->name);
2086 free((*artwork_node)->name_sorting);
2088 (*artwork_node)->identifier = getStringCopy(level_node->filename);
2089 (*artwork_node)->name = getStringCopy(level_node->name);
2090 (*artwork_node)->name_sorting = getStringCopy(level_node->name);
2092 (*artwork_node)->sort_priority = level_node->sort_priority;
2093 (*artwork_node)->color = LEVELCOLOR((*artwork_node));
2099 if (level_node->node_group != NULL)
2100 LoadArtworkInfoFromLevelInfo(artwork_node, level_node->node_group);
2102 level_node = level_node->next;
2106 void LoadLevelArtworkInfo()
2108 DrawInitText("Looking for custom level artwork:", 120, FC_GREEN);
2110 LoadArtworkInfoFromLevelInfo(&artwork.gfx_first, leveldir_first);
2111 LoadArtworkInfoFromLevelInfo(&artwork.snd_first, leveldir_first);
2112 LoadArtworkInfoFromLevelInfo(&artwork.mus_first, leveldir_first);
2114 /* needed for reloading level artwork not known at ealier stage */
2115 if (strcmp(artwork.gfx_current_identifier, setup.graphics_set) != 0)
2117 artwork.gfx_current =
2118 getTreeInfoFromIdentifier(artwork.gfx_first, setup.graphics_set);
2119 if (artwork.gfx_current == NULL)
2120 artwork.gfx_current = getFirstValidTreeInfoEntry(artwork.gfx_first);
2123 if (strcmp(artwork.snd_current_identifier, setup.sounds_set) != 0)
2125 artwork.snd_current =
2126 getTreeInfoFromIdentifier(artwork.snd_first, setup.sounds_set);
2127 if (artwork.snd_current == NULL)
2128 artwork.snd_current = getFirstValidTreeInfoEntry(artwork.snd_first);
2131 if (strcmp(artwork.mus_current_identifier, setup.music_set) != 0)
2133 artwork.mus_current =
2134 getTreeInfoFromIdentifier(artwork.mus_first, setup.music_set);
2135 if (artwork.mus_current == NULL)
2136 artwork.mus_current = getFirstValidTreeInfoEntry(artwork.mus_first);
2139 sortTreeInfo(&artwork.gfx_first, compareTreeInfoEntries);
2140 sortTreeInfo(&artwork.snd_first, compareTreeInfoEntries);
2141 sortTreeInfo(&artwork.mus_first, compareTreeInfoEntries);
2144 dumpTreeInfo(artwork.gfx_first, 0);
2145 dumpTreeInfo(artwork.snd_first, 0);
2146 dumpTreeInfo(artwork.mus_first, 0);
2150 static void SaveUserLevelInfo()
2156 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
2158 if (!(file = fopen(filename, MODE_WRITE)))
2160 Error(ERR_WARN, "cannot write level info file '%s'", filename);
2165 /* always start with reliable default values */
2166 setTreeInfoToDefaults(&ldi, TREE_TYPE_LEVEL_DIR);
2168 ldi.name = getStringCopy(getLoginName());
2169 ldi.author = getStringCopy(getRealName());
2171 ldi.first_level = 1;
2172 ldi.sort_priority = LEVELCLASS_USER_START;
2173 ldi.readonly = FALSE;
2174 ldi.graphics_set = getStringCopy(GRAPHICS_SUBDIR);
2175 ldi.sounds_set = getStringCopy(SOUNDS_SUBDIR);
2176 ldi.music_set = getStringCopy(MUSIC_SUBDIR);
2178 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2179 getCookie("LEVELINFO")));
2181 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
2182 if (i != LEVELINFO_TOKEN_IDENTIFIER &&
2183 i != LEVELINFO_TOKEN_NAME_SORTING &&
2184 i != LEVELINFO_TOKEN_IMPORTED_FROM)
2185 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
2190 SetFilePermissions(filename, PERMS_PRIVATE);
2193 char *getSetupValue(int type, void *value)
2195 static char value_string[MAX_LINE_LEN];
2203 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
2207 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
2211 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
2215 strcpy(value_string, getKeyNameFromKey(*(Key *)value));
2219 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
2223 sprintf(value_string, "%d", *(int *)value);
2227 strcpy(value_string, *(char **)value);
2231 value_string[0] = '\0';
2235 return value_string;
2238 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
2242 static char token_string[MAX_LINE_LEN];
2243 int token_type = token_info[token_nr].type;
2244 void *setup_value = token_info[token_nr].value;
2245 char *token_text = token_info[token_nr].text;
2246 char *value_string = getSetupValue(token_type, setup_value);
2248 /* build complete token string */
2249 sprintf(token_string, "%s%s", prefix, token_text);
2251 /* build setup entry line */
2252 line = getFormattedSetupEntry(token_string, value_string);
2254 if (token_type == TYPE_KEY_X11)
2256 Key key = *(Key *)setup_value;
2257 char *keyname = getKeyNameFromKey(key);
2259 /* add comment, if useful */
2260 if (strcmp(keyname, "(undefined)") != 0 &&
2261 strcmp(keyname, "(unknown)") != 0)
2263 /* add at least one whitespace */
2265 for (i=strlen(line); i<TOKEN_COMMENT_POSITION; i++)
2269 strcat(line, keyname);
2276 void LoadLevelSetup_LastSeries()
2279 SetupFileHash *level_setup_hash = NULL;
2281 /* always start with reliable default values */
2282 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2284 /* ----------------------------------------------------------------------- */
2285 /* ~/.<program>/levelsetup.conf */
2286 /* ----------------------------------------------------------------------- */
2288 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2290 if ((level_setup_hash = loadSetupFileHash(filename)))
2292 char *last_level_series =
2293 getHashEntry(level_setup_hash, TOKEN_STR_LAST_LEVEL_SERIES);
2295 leveldir_current = getTreeInfoFromIdentifier(leveldir_first,
2297 if (leveldir_current == NULL)
2298 leveldir_current = getFirstValidTreeInfoEntry(leveldir_first);
2300 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2302 freeSetupFileHash(level_setup_hash);
2305 Error(ERR_WARN, "using default setup values");
2310 void SaveLevelSetup_LastSeries()
2313 char *level_subdir = leveldir_current->filename;
2316 /* ----------------------------------------------------------------------- */
2317 /* ~/.<program>/levelsetup.conf */
2318 /* ----------------------------------------------------------------------- */
2320 InitUserDataDirectory();
2322 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
2324 if (!(file = fopen(filename, MODE_WRITE)))
2326 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2331 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2332 getCookie("LEVELSETUP")));
2333 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
2339 SetFilePermissions(filename, PERMS_PRIVATE);
2342 static void checkSeriesInfo()
2344 static char *level_directory = NULL;
2346 struct dirent *dir_entry;
2348 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
2350 level_directory = getPath2((leveldir_current->user_defined ?
2351 getUserLevelDir(NULL) :
2352 options.level_directory),
2353 leveldir_current->fullpath);
2355 if ((dir = opendir(level_directory)) == NULL)
2357 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
2361 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
2363 if (strlen(dir_entry->d_name) > 4 &&
2364 dir_entry->d_name[3] == '.' &&
2365 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
2367 char levelnum_str[4];
2370 strncpy(levelnum_str, dir_entry->d_name, 3);
2371 levelnum_str[3] = '\0';
2373 levelnum_value = atoi(levelnum_str);
2376 if (levelnum_value < leveldir_current->first_level)
2378 Error(ERR_WARN, "additional level %d found", levelnum_value);
2379 leveldir_current->first_level = levelnum_value;
2381 else if (levelnum_value > leveldir_current->last_level)
2383 Error(ERR_WARN, "additional level %d found", levelnum_value);
2384 leveldir_current->last_level = levelnum_value;
2393 void LoadLevelSetup_SeriesInfo()
2396 SetupFileHash *level_setup_hash = NULL;
2397 char *level_subdir = leveldir_current->filename;
2399 /* always start with reliable default values */
2400 level_nr = leveldir_current->first_level;
2402 checkSeriesInfo(leveldir_current);
2404 /* ----------------------------------------------------------------------- */
2405 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2406 /* ----------------------------------------------------------------------- */
2408 level_subdir = leveldir_current->filename;
2410 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2412 if ((level_setup_hash = loadSetupFileHash(filename)))
2416 token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL);
2420 level_nr = atoi(token_value);
2422 if (level_nr < leveldir_current->first_level)
2423 level_nr = leveldir_current->first_level;
2424 if (level_nr > leveldir_current->last_level)
2425 level_nr = leveldir_current->last_level;
2428 token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL);
2432 int level_nr = atoi(token_value);
2434 if (level_nr < leveldir_current->first_level)
2435 level_nr = leveldir_current->first_level;
2436 if (level_nr > leveldir_current->last_level + 1)
2437 level_nr = leveldir_current->last_level;
2439 if (leveldir_current->user_defined)
2440 level_nr = leveldir_current->last_level;
2442 leveldir_current->handicap_level = level_nr;
2445 checkSetupFileHashIdentifier(level_setup_hash, getCookie("LEVELSETUP"));
2447 freeSetupFileHash(level_setup_hash);
2450 Error(ERR_WARN, "using default setup values");
2455 void SaveLevelSetup_SeriesInfo()
2458 char *level_subdir = leveldir_current->filename;
2459 char *level_nr_str = int2str(level_nr, 0);
2460 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
2463 /* ----------------------------------------------------------------------- */
2464 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
2465 /* ----------------------------------------------------------------------- */
2467 InitLevelSetupDirectory(level_subdir);
2469 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
2471 if (!(file = fopen(filename, MODE_WRITE)))
2473 Error(ERR_WARN, "cannot write setup file '%s'", filename);
2478 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
2479 getCookie("LEVELSETUP")));
2480 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
2482 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
2483 handicap_level_str));
2488 SetFilePermissions(filename, PERMS_PRIVATE);