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 ***********************************************************/
24 /* file names and filename extensions */
25 #if !defined(PLATFORM_MSDOS)
26 #define LEVELSETUP_DIRECTORY "levelsetup"
27 #define SETUP_FILENAME "setup.conf"
28 #define LEVELSETUP_FILENAME "levelsetup.conf"
29 #define LEVELINFO_FILENAME "levelinfo.conf"
30 #define LEVELFILE_EXTENSION "level"
31 #define TAPEFILE_EXTENSION "tape"
32 #define SCOREFILE_EXTENSION "score"
34 #define LEVELSETUP_DIRECTORY "lvlsetup"
35 #define SETUP_FILENAME "setup.cnf"
36 #define LEVELSETUP_FILENAME "lvlsetup.cnf"
37 #define LEVELINFO_FILENAME "lvlinfo.cnf"
38 #define LEVELFILE_EXTENSION "lvl"
39 #define TAPEFILE_EXTENSION "tap"
40 #define SCOREFILE_EXTENSION "sco"
43 #define NUM_LEVELCLASS_DESC 8
44 static char *levelclass_desc[NUM_LEVELCLASS_DESC] =
56 #define LEVELCOLOR(n) (IS_LEVELCLASS_TUTORIAL(n) ? FC_BLUE : \
57 IS_LEVELCLASS_CLASSICS(n) ? FC_RED : \
58 IS_LEVELCLASS_BD(n) ? FC_GREEN : \
59 IS_LEVELCLASS_EM(n) ? FC_YELLOW : \
60 IS_LEVELCLASS_SP(n) ? FC_GREEN : \
61 IS_LEVELCLASS_DX(n) ? FC_YELLOW : \
62 IS_LEVELCLASS_CONTRIBUTION(n) ? FC_GREEN : \
63 IS_LEVELCLASS_USER(n) ? FC_RED : \
66 #define LEVELSORTING(n) (IS_LEVELCLASS_TUTORIAL(n) ? 0 : \
67 IS_LEVELCLASS_CLASSICS(n) ? 1 : \
68 IS_LEVELCLASS_BD(n) ? 2 : \
69 IS_LEVELCLASS_EM(n) ? 3 : \
70 IS_LEVELCLASS_SP(n) ? 4 : \
71 IS_LEVELCLASS_DX(n) ? 5 : \
72 IS_LEVELCLASS_CONTRIBUTION(n) ? 6 : \
73 IS_LEVELCLASS_USER(n) ? 7 : \
76 #define TOKEN_VALUE_POSITION 30
78 #define MAX_COOKIE_LEN 256
81 /* ------------------------------------------------------------------------- */
83 /* ------------------------------------------------------------------------- */
85 char *getLevelClassDescription(struct LevelDirInfo *ldi)
87 int position = ldi->sort_priority / 100;
89 if (position >= 0 && position < NUM_LEVELCLASS_DESC)
90 return levelclass_desc[position];
92 return "Unknown Level Class";
95 static char *getUserLevelDir(char *level_subdir)
97 static char *userlevel_dir = NULL;
98 char *data_dir = getUserDataDir();
99 char *userlevel_subdir = LEVELS_DIRECTORY;
104 if (strlen(level_subdir) > 0)
105 userlevel_dir = getPath3(data_dir, userlevel_subdir, level_subdir);
107 userlevel_dir = getPath2(data_dir, userlevel_subdir);
109 return userlevel_dir;
112 static char *getTapeDir(char *level_subdir)
114 static char *tape_dir = NULL;
115 char *data_dir = getUserDataDir();
116 char *tape_subdir = TAPES_DIRECTORY;
121 if (strlen(level_subdir) > 0)
122 tape_dir = getPath3(data_dir, tape_subdir, level_subdir);
124 tape_dir = getPath2(data_dir, tape_subdir);
129 static char *getScoreDir(char *level_subdir)
131 static char *score_dir = NULL;
132 char *data_dir = options.rw_base_directory;
133 char *score_subdir = SCORES_DIRECTORY;
138 if (strlen(level_subdir) > 0)
139 score_dir = getPath3(data_dir, score_subdir, level_subdir);
141 score_dir = getPath2(data_dir, score_subdir);
146 static char *getLevelSetupDir(char *level_subdir)
148 static char *levelsetup_dir = NULL;
149 char *data_dir = getUserDataDir();
150 char *levelsetup_subdir = LEVELSETUP_DIRECTORY;
153 free(levelsetup_dir);
155 if (strlen(level_subdir) > 0)
156 levelsetup_dir = getPath3(data_dir, levelsetup_subdir, level_subdir);
158 levelsetup_dir = getPath2(data_dir, levelsetup_subdir);
160 return levelsetup_dir;
163 char *getLevelFilename(int nr)
165 static char *filename = NULL;
166 char basename[MAX_FILENAME_LEN];
168 if (filename != NULL)
171 sprintf(basename, "%03d.%s", nr, LEVELFILE_EXTENSION);
172 filename = getPath3((leveldir_current->user_defined ?
173 getUserLevelDir("") :
174 options.level_directory),
175 leveldir_current->fullpath,
181 char *getTapeFilename(int nr)
183 static char *filename = NULL;
184 char basename[MAX_FILENAME_LEN];
186 if (filename != NULL)
189 sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION);
190 filename = getPath2(getTapeDir(leveldir_current->filename), basename);
195 char *getScoreFilename(int nr)
197 static char *filename = NULL;
198 char basename[MAX_FILENAME_LEN];
200 if (filename != NULL)
203 sprintf(basename, "%03d.%s", nr, SCOREFILE_EXTENSION);
204 filename = getPath2(getScoreDir(leveldir_current->filename), basename);
209 char *getSetupFilename()
211 static char *filename = NULL;
213 if (filename != NULL)
216 filename = getPath2(getSetupDir(), SETUP_FILENAME);
221 static char *getImageBasename(char *basename)
223 char *result = basename;
225 #if defined(PLATFORM_MSDOS)
226 if (program.filename_prefix != NULL)
228 int prefix_len = strlen(program.filename_prefix);
230 if (strncmp(basename, program.filename_prefix, prefix_len) == 0)
231 result = &basename[prefix_len];
238 char *getImageFilename(char *basename)
240 static char *filename = NULL;
242 if (filename != NULL)
245 filename = getPath2(options.graphics_directory, getImageBasename(basename));
250 void InitTapeDirectory(char *level_subdir)
252 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
253 createDirectory(getTapeDir(""), "main tape", PERMS_PRIVATE);
254 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
257 void InitScoreDirectory(char *level_subdir)
259 createDirectory(getScoreDir(""), "main score", PERMS_PUBLIC);
260 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
263 static void SaveUserLevelInfo();
265 void InitUserLevelDirectory(char *level_subdir)
267 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
269 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
270 createDirectory(getUserLevelDir(""), "main user level", PERMS_PRIVATE);
271 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
277 void InitLevelSetupDirectory(char *level_subdir)
279 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
280 createDirectory(getLevelSetupDir(""), "main level setup", PERMS_PRIVATE);
281 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
284 void ReadChunk_VERS(FILE *file, int *file_version, int *game_version)
286 int file_version_major, file_version_minor, file_version_patch;
287 int game_version_major, game_version_minor, game_version_patch;
289 file_version_major = fgetc(file);
290 file_version_minor = fgetc(file);
291 file_version_patch = fgetc(file);
292 fgetc(file); /* not used */
294 game_version_major = fgetc(file);
295 game_version_minor = fgetc(file);
296 game_version_patch = fgetc(file);
297 fgetc(file); /* not used */
299 *file_version = VERSION_IDENT(file_version_major,
303 *game_version = VERSION_IDENT(game_version_major,
308 void WriteChunk_VERS(FILE *file, int file_version, int game_version)
310 int file_version_major = VERSION_MAJOR(file_version);
311 int file_version_minor = VERSION_MINOR(file_version);
312 int file_version_patch = VERSION_PATCH(file_version);
313 int game_version_major = VERSION_MAJOR(game_version);
314 int game_version_minor = VERSION_MINOR(game_version);
315 int game_version_patch = VERSION_PATCH(game_version);
317 fputc(file_version_major, file);
318 fputc(file_version_minor, file);
319 fputc(file_version_patch, file);
320 fputc(0, file); /* not used */
322 fputc(game_version_major, file);
323 fputc(game_version_minor, file);
324 fputc(game_version_patch, file);
325 fputc(0, file); /* not used */
329 /* ------------------------------------------------------------------------- */
330 /* some functions to handle lists of level directories */
331 /* ------------------------------------------------------------------------- */
333 struct LevelDirInfo *newLevelDirInfo()
335 return checked_calloc(sizeof(struct LevelDirInfo));
338 void pushLevelDirInfo(struct LevelDirInfo **node_first,
339 struct LevelDirInfo *node_new)
341 node_new->next = *node_first;
342 *node_first = node_new;
345 int numLevelDirInfo(struct LevelDirInfo *node)
358 boolean validLevelSeries(struct LevelDirInfo *node)
360 return (node != NULL && !node->node_group && !node->parent_link);
363 struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *node)
367 if (leveldir_first) /* start with first level directory entry */
368 return getFirstValidLevelSeries(leveldir_first);
372 else if (node->node_group) /* enter level group (step down into tree) */
373 return getFirstValidLevelSeries(node->node_group);
374 else if (node->parent_link) /* skip start entry of level group */
376 if (node->next) /* get first real level series entry */
377 return getFirstValidLevelSeries(node->next);
378 else /* leave empty level group and go on */
379 return getFirstValidLevelSeries(node->node_parent->next);
381 else /* this seems to be a regular level series */
385 struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *node)
390 if (node->node_parent == NULL) /* top level group */
391 return leveldir_first;
392 else /* sub level group */
393 return node->node_parent->node_group;
396 int numLevelDirInfoInGroup(struct LevelDirInfo *node)
398 return numLevelDirInfo(getLevelDirInfoFirstGroupEntry(node));
401 int posLevelDirInfo(struct LevelDirInfo *node)
403 struct LevelDirInfo *node_cmp = getLevelDirInfoFirstGroupEntry(node);
408 if (node_cmp == node)
412 node_cmp = node_cmp->next;
418 struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *node, int pos)
420 struct LevelDirInfo *node_default = node;
435 struct LevelDirInfo *getLevelDirInfoFromFilenameExt(struct LevelDirInfo *node,
438 if (filename == NULL)
443 if (node->node_group)
445 struct LevelDirInfo *node_group;
447 node_group = getLevelDirInfoFromFilenameExt(node->node_group, filename);
452 else if (!node->parent_link)
454 if (strcmp(filename, node->filename) == 0)
464 struct LevelDirInfo *getLevelDirInfoFromFilename(char *filename)
466 return getLevelDirInfoFromFilenameExt(leveldir_first, filename);
469 void dumpLevelDirInfo(struct LevelDirInfo *node, int depth)
475 for (i=0; i<depth * 3; i++)
478 printf("filename == '%s'\n", node->filename);
480 if (node->node_group != NULL)
481 dumpLevelDirInfo(node->node_group, depth + 1);
487 void sortLevelDirInfo(struct LevelDirInfo **node_first,
488 int (*compare_function)(const void *, const void *))
490 int num_nodes = numLevelDirInfo(*node_first);
491 struct LevelDirInfo **sort_array;
492 struct LevelDirInfo *node = *node_first;
498 /* allocate array for sorting structure pointers */
499 sort_array = checked_calloc(num_nodes * sizeof(struct LevelDirInfo *));
501 /* writing structure pointers to sorting array */
502 while (i < num_nodes && node) /* double boundary check... */
504 sort_array[i] = node;
510 /* sorting the structure pointers in the sorting array */
511 qsort(sort_array, num_nodes, sizeof(struct LevelDirInfo *),
514 /* update the linkage of list elements with the sorted node array */
515 for (i=0; i<num_nodes - 1; i++)
516 sort_array[i]->next = sort_array[i + 1];
517 sort_array[num_nodes - 1]->next = NULL;
519 /* update the linkage of the main list anchor pointer */
520 *node_first = sort_array[0];
524 /* now recursively sort the level group structures */
528 if (node->node_group != NULL)
529 sortLevelDirInfo(&node->node_group, compare_function);
536 /* ========================================================================= */
537 /* some stuff from "files.c" */
538 /* ========================================================================= */
540 #if defined(PLATFORM_WIN32)
542 #define S_IRGRP S_IRUSR
545 #define S_IROTH S_IRUSR
548 #define S_IWGRP S_IWUSR
551 #define S_IWOTH S_IWUSR
554 #define S_IXGRP S_IXUSR
557 #define S_IXOTH S_IXUSR
560 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
565 #endif /* PLATFORM_WIN32 */
567 /* file permissions for newly written files */
568 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
569 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
570 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
572 #define MODE_W_PRIVATE (S_IWUSR)
573 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
574 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
576 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
577 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
579 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
580 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
582 char *getUserDataDir(void)
584 static char *userdata_dir = NULL;
588 char *home_dir = getHomeDir();
589 char *data_dir = program.userdata_directory;
591 userdata_dir = getPath2(home_dir, data_dir);
599 return getUserDataDir();
602 static mode_t posix_umask(mode_t mask)
604 #if defined(PLATFORM_UNIX)
611 static int posix_mkdir(const char *pathname, mode_t mode)
613 #if defined(PLATFORM_WIN32)
614 return mkdir(pathname);
616 return mkdir(pathname, mode);
620 void createDirectory(char *dir, char *text, int permission_class)
622 /* leave "other" permissions in umask untouched, but ensure group parts
623 of USERDATA_DIR_MODE are not masked */
624 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
625 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
626 mode_t normal_umask = posix_umask(0);
627 mode_t group_umask = ~(dir_mode & S_IRWXG);
628 posix_umask(normal_umask & group_umask);
630 if (access(dir, F_OK) != 0)
631 if (posix_mkdir(dir, dir_mode) != 0)
632 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
634 posix_umask(normal_umask); /* reset normal umask */
637 void InitUserDataDirectory()
639 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
642 void SetFilePermissions(char *filename, int permission_class)
644 chmod(filename, (permission_class == PERMS_PRIVATE ?
645 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
648 char *getCookie(char *file_type)
650 static char cookie[MAX_COOKIE_LEN + 1];
652 if (strlen(program.cookie_prefix) + 1 +
653 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
654 return "[COOKIE ERROR]"; /* should never happen */
656 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
657 program.cookie_prefix, file_type,
658 program.version_major, program.version_minor);
663 int getFileVersionFromCookieString(const char *cookie)
665 const char *ptr_cookie1, *ptr_cookie2;
666 const char *pattern1 = "_FILE_VERSION_";
667 const char *pattern2 = "?.?";
668 const int len_cookie = strlen(cookie);
669 const int len_pattern1 = strlen(pattern1);
670 const int len_pattern2 = strlen(pattern2);
671 const int len_pattern = len_pattern1 + len_pattern2;
672 int version_major, version_minor;
674 if (len_cookie <= len_pattern)
677 ptr_cookie1 = &cookie[len_cookie - len_pattern];
678 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
680 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
683 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
684 ptr_cookie2[1] != '.' ||
685 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
688 version_major = ptr_cookie2[0] - '0';
689 version_minor = ptr_cookie2[2] - '0';
691 return VERSION_IDENT(version_major, version_minor, 0);
694 boolean checkCookieString(const char *cookie, const char *template)
696 const char *pattern = "_FILE_VERSION_?.?";
697 const int len_cookie = strlen(cookie);
698 const int len_template = strlen(template);
699 const int len_pattern = strlen(pattern);
701 if (len_cookie != len_template)
704 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
710 /* ------------------------------------------------------------------------- */
711 /* setup file list handling functions */
712 /* ------------------------------------------------------------------------- */
714 int get_string_integer_value(char *s)
716 static char *number_text[][3] =
718 { "0", "zero", "null", },
719 { "1", "one", "first" },
720 { "2", "two", "second" },
721 { "3", "three", "third" },
722 { "4", "four", "fourth" },
723 { "5", "five", "fifth" },
724 { "6", "six", "sixth" },
725 { "7", "seven", "seventh" },
726 { "8", "eight", "eighth" },
727 { "9", "nine", "ninth" },
728 { "10", "ten", "tenth" },
729 { "11", "eleven", "eleventh" },
730 { "12", "twelve", "twelfth" },
734 char *s_lower = getStringToLower(s);
739 if (strcmp(s_lower, number_text[i][j]) == 0)
750 boolean get_string_boolean_value(char *s)
752 char *s_lower = getStringToLower(s);
753 boolean result = FALSE;
755 if (strcmp(s_lower, "true") == 0 ||
756 strcmp(s_lower, "yes") == 0 ||
757 strcmp(s_lower, "on") == 0 ||
758 get_string_integer_value(s) == 1)
766 char *getFormattedSetupEntry(char *token, char *value)
769 static char entry[MAX_LINE_LEN];
771 sprintf(entry, "%s:", token);
772 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
776 strcat(entry, value);
781 void freeSetupFileList(struct SetupFileList *setup_file_list)
783 if (!setup_file_list)
786 if (setup_file_list->token)
787 free(setup_file_list->token);
788 if (setup_file_list->value)
789 free(setup_file_list->value);
790 if (setup_file_list->next)
791 freeSetupFileList(setup_file_list->next);
792 free(setup_file_list);
795 static struct SetupFileList *newSetupFileList(char *token, char *value)
797 struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
799 new->token = checked_malloc(strlen(token) + 1);
800 strcpy(new->token, token);
802 new->value = checked_malloc(strlen(value) + 1);
803 strcpy(new->value, value);
810 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
812 if (!setup_file_list)
815 if (strcmp(setup_file_list->token, token) == 0)
816 return setup_file_list->value;
818 return getTokenValue(setup_file_list->next, token);
821 static void setTokenValue(struct SetupFileList *setup_file_list,
822 char *token, char *value)
824 if (!setup_file_list)
827 if (strcmp(setup_file_list->token, token) == 0)
829 free(setup_file_list->value);
830 setup_file_list->value = checked_malloc(strlen(value) + 1);
831 strcpy(setup_file_list->value, value);
833 else if (setup_file_list->next == NULL)
834 setup_file_list->next = newSetupFileList(token, value);
836 setTokenValue(setup_file_list->next, token, value);
840 static void printSetupFileList(struct SetupFileList *setup_file_list)
842 if (!setup_file_list)
845 printf("token: '%s'\n", setup_file_list->token);
846 printf("value: '%s'\n", setup_file_list->value);
848 printSetupFileList(setup_file_list->next);
852 struct SetupFileList *loadSetupFileList(char *filename)
855 char line[MAX_LINE_LEN];
856 char *token, *value, *line_ptr;
857 struct SetupFileList *setup_file_list = newSetupFileList("", "");
858 struct SetupFileList *first_valid_list_entry;
862 if (!(file = fopen(filename, MODE_READ)))
864 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
870 /* read next line of input file */
871 if (!fgets(line, MAX_LINE_LEN, file))
874 /* cut trailing comment or whitespace from input line */
875 for (line_ptr = line; *line_ptr; line_ptr++)
877 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
884 /* cut trailing whitespaces from input line */
885 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
886 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
889 /* ignore empty lines */
893 line_len = strlen(line);
895 /* cut leading whitespaces from token */
896 for (token = line; *token; token++)
897 if (*token != ' ' && *token != '\t')
900 /* find end of token */
901 for (line_ptr = token; *line_ptr; line_ptr++)
903 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
910 if (line_ptr < line + line_len)
911 value = line_ptr + 1;
915 /* cut leading whitespaces from value */
916 for (; *value; value++)
917 if (*value != ' ' && *value != '\t')
920 if (*token && *value)
921 setTokenValue(setup_file_list, token, value);
926 first_valid_list_entry = setup_file_list->next;
928 /* free empty list header */
929 setup_file_list->next = NULL;
930 freeSetupFileList(setup_file_list);
932 if (first_valid_list_entry == NULL)
933 Error(ERR_WARN, "configuration file '%s' is empty", filename);
935 return first_valid_list_entry;
938 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
941 if (!setup_file_list)
944 if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
946 if (!checkCookieString(setup_file_list->value, identifier))
948 Error(ERR_WARN, "configuration file has wrong file identifier");
955 if (setup_file_list->next)
956 checkSetupFileListIdentifier(setup_file_list->next, identifier);
959 Error(ERR_WARN, "configuration file has no file identifier");
965 /* ========================================================================= */
966 /* setup file stuff */
967 /* ========================================================================= */
969 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
970 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
971 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
973 /* level directory info */
974 #define LEVELINFO_TOKEN_NAME 0
975 #define LEVELINFO_TOKEN_NAME_SHORT 1
976 #define LEVELINFO_TOKEN_NAME_SORTING 2
977 #define LEVELINFO_TOKEN_AUTHOR 3
978 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
979 #define LEVELINFO_TOKEN_LEVELS 5
980 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
981 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
982 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
983 #define LEVELINFO_TOKEN_READONLY 9
985 #define NUM_LEVELINFO_TOKENS 10
987 static struct LevelDirInfo ldi;
989 static struct TokenInfo levelinfo_tokens[] =
991 /* level directory info */
992 { TYPE_STRING, &ldi.name, "name" },
993 { TYPE_STRING, &ldi.name_short, "name_short" },
994 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
995 { TYPE_STRING, &ldi.author, "author" },
996 { TYPE_STRING, &ldi.imported_from, "imported_from" },
997 { TYPE_INTEGER, &ldi.levels, "levels" },
998 { TYPE_INTEGER, &ldi.first_level, "first_level" },
999 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1000 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1001 { TYPE_BOOLEAN, &ldi.readonly, "readonly" }
1004 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1006 ldi->filename = NULL;
1007 ldi->fullpath = NULL;
1008 ldi->basepath = NULL;
1009 ldi->name = getStringCopy(ANONYMOUS_NAME);
1010 ldi->name_short = NULL;
1011 ldi->name_sorting = NULL;
1012 ldi->author = getStringCopy(ANONYMOUS_NAME);
1013 ldi->imported_from = NULL;
1015 ldi->first_level = 0;
1016 ldi->last_level = 0;
1017 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1018 ldi->level_group = FALSE;
1019 ldi->parent_link = FALSE;
1020 ldi->user_defined = FALSE;
1021 ldi->readonly = TRUE;
1023 ldi->class_desc = NULL;
1024 ldi->handicap_level = 0;
1026 ldi->cl_cursor = -1;
1028 ldi->node_parent = NULL;
1029 ldi->node_group = NULL;
1033 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1034 struct LevelDirInfo *parent)
1038 setLevelDirInfoToDefaults(ldi);
1042 /* first copy all values from the parent structure ... */
1045 /* ... then set all fields to default that cannot be inherited from parent.
1046 This is especially important for all those fields that can be set from
1047 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1048 calls 'free()' for all already set token values which requires that no
1049 other structure's pointer may point to them!
1052 ldi->filename = NULL;
1053 ldi->fullpath = NULL;
1054 ldi->basepath = NULL;
1055 ldi->name = getStringCopy(ANONYMOUS_NAME);
1056 ldi->name_short = NULL;
1057 ldi->name_sorting = NULL;
1058 ldi->author = getStringCopy(parent->author);
1059 ldi->imported_from = getStringCopy(parent->imported_from);
1061 ldi->level_group = FALSE;
1062 ldi->parent_link = FALSE;
1064 ldi->node_parent = parent;
1065 ldi->node_group = NULL;
1069 void setSetupInfo(struct TokenInfo *token_info,
1070 int token_nr, char *token_value)
1072 int token_type = token_info[token_nr].type;
1073 void *setup_value = token_info[token_nr].value;
1075 if (token_value == NULL)
1078 /* set setup field to corresponding token value */
1083 *(boolean *)setup_value = get_string_boolean_value(token_value);
1087 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1091 *(int *)setup_value = get_string_integer_value(token_value);
1095 if (*(char **)setup_value != NULL)
1096 free(*(char **)setup_value);
1097 *(char **)setup_value = getStringCopy(token_value);
1105 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1107 const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1108 const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1111 if (entry1->parent_link || entry2->parent_link)
1112 compare_result = (entry1->parent_link ? -1 : +1);
1113 else if (entry1->sort_priority == entry2->sort_priority)
1115 char *name1 = getStringToLower(entry1->name_sorting);
1116 char *name2 = getStringToLower(entry2->name_sorting);
1118 compare_result = strcmp(name1, name2);
1123 else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1124 compare_result = entry1->sort_priority - entry2->sort_priority;
1126 compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1128 return compare_result;
1131 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1133 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1135 setLevelDirInfoToDefaults(leveldir_new);
1137 leveldir_new->node_parent = node_parent;
1138 leveldir_new->parent_link = TRUE;
1140 leveldir_new->name = ".. (parent directory)";
1141 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1142 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1144 leveldir_new->filename = "..";
1145 leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1147 leveldir_new->sort_priority = node_parent->sort_priority;
1148 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1150 pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1153 /* forward declaration for recursive call by "LoadLevelInfoFromSetupFile()" */
1154 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **,
1155 struct LevelDirInfo *,
1158 static boolean LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1159 struct LevelDirInfo *node_parent,
1160 char *level_directory,
1161 char *directory_name)
1163 char *directory_path = getPath2(level_directory, directory_name);
1164 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1165 struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1166 struct LevelDirInfo *leveldir_new = NULL;
1169 if (setup_file_list == NULL)
1171 Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1173 free(directory_path);
1179 leveldir_new = newLevelDirInfo();
1181 checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1182 setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1184 /* set all structure fields according to the token/value pairs */
1185 ldi = *leveldir_new;
1186 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1187 setSetupInfo(levelinfo_tokens, i,
1188 getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1189 *leveldir_new = ldi;
1191 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1193 if (leveldir_new->name_short == NULL)
1194 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1196 if (leveldir_new->name_sorting == NULL)
1197 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1199 leveldir_new->filename = getStringCopy(directory_name);
1201 if (node_parent == NULL) /* top level group */
1203 leveldir_new->basepath = level_directory;
1204 leveldir_new->fullpath = leveldir_new->filename;
1206 else /* sub level group */
1208 leveldir_new->basepath = node_parent->basepath;
1209 leveldir_new->fullpath = getPath2(node_parent->fullpath,
1213 if (leveldir_new->levels < 1)
1214 leveldir_new->levels = 1;
1216 leveldir_new->last_level =
1217 leveldir_new->first_level + leveldir_new->levels - 1;
1219 leveldir_new->user_defined =
1220 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1222 leveldir_new->color = LEVELCOLOR(leveldir_new);
1223 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1225 leveldir_new->handicap_level = /* set handicap to default value */
1226 (leveldir_new->user_defined ?
1227 leveldir_new->last_level :
1228 leveldir_new->first_level);
1230 pushLevelDirInfo(node_first, leveldir_new);
1232 freeSetupFileList(setup_file_list);
1234 if (leveldir_new->level_group)
1236 /* create node to link back to current level directory */
1237 createParentLevelDirNode(leveldir_new);
1239 /* step into sub-directory and look for more level series */
1240 LoadLevelInfoFromLevelGroupDir(&leveldir_new->node_group,
1241 leveldir_new, directory_path);
1244 free(directory_path);
1250 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **node_first,
1251 struct LevelDirInfo *node_parent,
1252 char *level_directory)
1255 struct dirent *dir_entry;
1256 boolean valid_entry_found = FALSE;
1258 if ((dir = opendir(level_directory)) == NULL)
1260 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1264 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1266 struct stat file_status;
1267 char *directory_name = dir_entry->d_name;
1268 char *directory_path = getPath2(level_directory, directory_name);
1270 /* skip entries for current and parent directory */
1271 if (strcmp(directory_name, ".") == 0 ||
1272 strcmp(directory_name, "..") == 0)
1274 free(directory_path);
1278 /* find out if directory entry is itself a directory */
1279 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1280 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1282 free(directory_path);
1286 free(directory_path);
1288 valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1295 if (!valid_entry_found)
1297 /* check if this directory directly contains a file "levelinfo.conf" */
1298 valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1299 level_directory, ".");
1302 if (!valid_entry_found)
1303 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1307 void LoadLevelInfo()
1309 InitUserLevelDirectory(getLoginName());
1311 DrawInitText("Loading level series:", 120, FC_GREEN);
1313 /* check if this directory directly contains a file "levelinfo.conf" */
1314 LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,options.level_directory);
1315 LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,getUserLevelDir(""));
1317 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1319 if (leveldir_first == NULL)
1320 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1322 sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1325 dumpLevelDirInfo(leveldir_first, 0);
1329 static void SaveUserLevelInfo()
1335 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1337 if (!(file = fopen(filename, MODE_WRITE)))
1339 Error(ERR_WARN, "cannot write level info file '%s'", filename);
1344 /* always start with reliable default values */
1345 setLevelDirInfoToDefaults(&ldi);
1347 ldi.name = getLoginName();
1348 ldi.author = getRealName();
1350 ldi.first_level = 1;
1351 ldi.sort_priority = LEVELCLASS_USER_START;
1352 ldi.readonly = FALSE;
1354 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1355 getCookie("LEVELINFO")));
1357 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1358 if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1359 i != LEVELINFO_TOKEN_NAME_SORTING &&
1360 i != LEVELINFO_TOKEN_IMPORTED_FROM)
1361 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1366 SetFilePermissions(filename, PERMS_PRIVATE);
1369 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1372 static char entry[MAX_LINE_LEN];
1373 int token_type = token_info[token_nr].type;
1374 void *setup_value = token_info[token_nr].value;
1375 char *token_text = token_info[token_nr].text;
1377 /* start with the prefix, token and some spaces to format output line */
1378 sprintf(entry, "%s%s:", prefix, token_text);
1379 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1382 /* continue with the token's value (which can have different types) */
1386 strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1390 strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1395 Key key = *(Key *)setup_value;
1396 char *keyname = getKeyNameFromKey(key);
1398 strcat(entry, getX11KeyNameFromKey(key));
1399 for (i=strlen(entry); i<50; i++)
1402 /* add comment, if useful */
1403 if (strcmp(keyname, "(undefined)") != 0 &&
1404 strcmp(keyname, "(unknown)") != 0)
1406 strcat(entry, "# ");
1407 strcat(entry, keyname);
1414 char buffer[MAX_LINE_LEN];
1416 sprintf(buffer, "%d", *(int *)setup_value);
1417 strcat(entry, buffer);
1422 strcat(entry, *(char **)setup_value);
1432 void LoadLevelSetup_LastSeries()
1435 struct SetupFileList *level_setup_list = NULL;
1437 /* always start with reliable default values */
1438 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1440 /* ----------------------------------------------------------------------- */
1441 /* ~/.<program>/levelsetup.conf */
1442 /* ----------------------------------------------------------------------- */
1444 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1446 if ((level_setup_list = loadSetupFileList(filename)))
1448 char *last_level_series =
1449 getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1451 leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1452 if (leveldir_current == NULL)
1453 leveldir_current = leveldir_first;
1455 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1457 freeSetupFileList(level_setup_list);
1460 Error(ERR_WARN, "using default setup values");
1465 void SaveLevelSetup_LastSeries()
1468 char *level_subdir = leveldir_current->filename;
1471 /* ----------------------------------------------------------------------- */
1472 /* ~/.<program>/levelsetup.conf */
1473 /* ----------------------------------------------------------------------- */
1475 InitUserDataDirectory();
1477 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1479 if (!(file = fopen(filename, MODE_WRITE)))
1481 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1486 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1487 getCookie("LEVELSETUP")));
1488 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1494 SetFilePermissions(filename, PERMS_PRIVATE);
1497 static void checkSeriesInfo()
1499 static char *level_directory = NULL;
1501 struct dirent *dir_entry;
1503 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1505 level_directory = getPath2((leveldir_current->user_defined ?
1506 getUserLevelDir("") :
1507 options.level_directory),
1508 leveldir_current->fullpath);
1510 if ((dir = opendir(level_directory)) == NULL)
1512 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1516 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
1518 if (strlen(dir_entry->d_name) > 4 &&
1519 dir_entry->d_name[3] == '.' &&
1520 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1522 char levelnum_str[4];
1525 strncpy(levelnum_str, dir_entry->d_name, 3);
1526 levelnum_str[3] = '\0';
1528 levelnum_value = atoi(levelnum_str);
1530 if (levelnum_value < leveldir_current->first_level)
1532 Error(ERR_WARN, "additional level %d found", levelnum_value);
1533 leveldir_current->first_level = levelnum_value;
1535 else if (levelnum_value > leveldir_current->last_level)
1537 Error(ERR_WARN, "additional level %d found", levelnum_value);
1538 leveldir_current->last_level = levelnum_value;
1546 void LoadLevelSetup_SeriesInfo()
1549 struct SetupFileList *level_setup_list = NULL;
1550 char *level_subdir = leveldir_current->filename;
1552 /* always start with reliable default values */
1553 level_nr = leveldir_current->first_level;
1555 checkSeriesInfo(leveldir_current);
1557 /* ----------------------------------------------------------------------- */
1558 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1559 /* ----------------------------------------------------------------------- */
1561 level_subdir = leveldir_current->filename;
1563 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1565 if ((level_setup_list = loadSetupFileList(filename)))
1569 token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1573 level_nr = atoi(token_value);
1575 if (level_nr < leveldir_current->first_level)
1576 level_nr = leveldir_current->first_level;
1577 if (level_nr > leveldir_current->last_level)
1578 level_nr = leveldir_current->last_level;
1581 token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1585 int level_nr = atoi(token_value);
1587 if (level_nr < leveldir_current->first_level)
1588 level_nr = leveldir_current->first_level;
1589 if (level_nr > leveldir_current->last_level + 1)
1590 level_nr = leveldir_current->last_level;
1592 if (leveldir_current->user_defined)
1593 level_nr = leveldir_current->last_level;
1595 leveldir_current->handicap_level = level_nr;
1598 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1600 freeSetupFileList(level_setup_list);
1603 Error(ERR_WARN, "using default setup values");
1608 void SaveLevelSetup_SeriesInfo()
1611 char *level_subdir = leveldir_current->filename;
1612 char *level_nr_str = int2str(level_nr, 0);
1613 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1616 /* ----------------------------------------------------------------------- */
1617 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1618 /* ----------------------------------------------------------------------- */
1620 InitLevelSetupDirectory(level_subdir);
1622 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1624 if (!(file = fopen(filename, MODE_WRITE)))
1626 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1631 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1632 getCookie("LEVELSETUP")));
1633 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1635 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1636 handicap_level_str));
1641 SetFilePermissions(filename, PERMS_PRIVATE);