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 void InitTapeDirectory(char *level_subdir)
223 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
224 createDirectory(getTapeDir(""), "main tape", PERMS_PRIVATE);
225 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
228 void InitScoreDirectory(char *level_subdir)
230 createDirectory(getScoreDir(""), "main score", PERMS_PUBLIC);
231 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
234 static void SaveUserLevelInfo();
236 void InitUserLevelDirectory(char *level_subdir)
238 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
240 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
241 createDirectory(getUserLevelDir(""), "main user level", PERMS_PRIVATE);
242 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
248 void InitLevelSetupDirectory(char *level_subdir)
250 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
251 createDirectory(getLevelSetupDir(""), "main level setup", PERMS_PRIVATE);
252 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
255 void ReadChunk_VERS(FILE *file, int *file_version, int *game_version)
257 int file_version_major, file_version_minor, file_version_patch;
258 int game_version_major, game_version_minor, game_version_patch;
260 file_version_major = fgetc(file);
261 file_version_minor = fgetc(file);
262 file_version_patch = fgetc(file);
263 fgetc(file); /* not used */
265 game_version_major = fgetc(file);
266 game_version_minor = fgetc(file);
267 game_version_patch = fgetc(file);
268 fgetc(file); /* not used */
270 *file_version = VERSION_IDENT(file_version_major,
274 *game_version = VERSION_IDENT(game_version_major,
279 void WriteChunk_VERS(FILE *file, int file_version, int game_version)
281 int file_version_major = VERSION_MAJOR(file_version);
282 int file_version_minor = VERSION_MINOR(file_version);
283 int file_version_patch = VERSION_PATCH(file_version);
284 int game_version_major = VERSION_MAJOR(game_version);
285 int game_version_minor = VERSION_MINOR(game_version);
286 int game_version_patch = VERSION_PATCH(game_version);
288 fputc(file_version_major, file);
289 fputc(file_version_minor, file);
290 fputc(file_version_patch, file);
291 fputc(0, file); /* not used */
293 fputc(game_version_major, file);
294 fputc(game_version_minor, file);
295 fputc(game_version_patch, file);
296 fputc(0, file); /* not used */
300 /* ------------------------------------------------------------------------- */
301 /* some functions to handle lists of level directories */
302 /* ------------------------------------------------------------------------- */
304 struct LevelDirInfo *newLevelDirInfo()
306 return checked_calloc(sizeof(struct LevelDirInfo));
309 void pushLevelDirInfo(struct LevelDirInfo **node_first,
310 struct LevelDirInfo *node_new)
312 node_new->next = *node_first;
313 *node_first = node_new;
316 int numLevelDirInfo(struct LevelDirInfo *node)
329 boolean validLevelSeries(struct LevelDirInfo *node)
331 return (node != NULL && !node->node_group && !node->parent_link);
334 struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *node)
338 if (leveldir_first) /* start with first level directory entry */
339 return getFirstValidLevelSeries(leveldir_first);
343 else if (node->node_group) /* enter level group (step down into tree) */
344 return getFirstValidLevelSeries(node->node_group);
345 else if (node->parent_link) /* skip start entry of level group */
347 if (node->next) /* get first real level series entry */
348 return getFirstValidLevelSeries(node->next);
349 else /* leave empty level group and go on */
350 return getFirstValidLevelSeries(node->node_parent->next);
352 else /* this seems to be a regular level series */
356 struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *node)
361 if (node->node_parent == NULL) /* top level group */
362 return leveldir_first;
363 else /* sub level group */
364 return node->node_parent->node_group;
367 int numLevelDirInfoInGroup(struct LevelDirInfo *node)
369 return numLevelDirInfo(getLevelDirInfoFirstGroupEntry(node));
372 int posLevelDirInfo(struct LevelDirInfo *node)
374 struct LevelDirInfo *node_cmp = getLevelDirInfoFirstGroupEntry(node);
379 if (node_cmp == node)
383 node_cmp = node_cmp->next;
389 struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *node, int pos)
391 struct LevelDirInfo *node_default = node;
406 struct LevelDirInfo *getLevelDirInfoFromFilenameExt(struct LevelDirInfo *node,
409 if (filename == NULL)
414 if (node->node_group)
416 struct LevelDirInfo *node_group;
418 node_group = getLevelDirInfoFromFilenameExt(node->node_group, filename);
423 else if (!node->parent_link)
425 if (strcmp(filename, node->filename) == 0)
435 struct LevelDirInfo *getLevelDirInfoFromFilename(char *filename)
437 return getLevelDirInfoFromFilenameExt(leveldir_first, filename);
440 void dumpLevelDirInfo(struct LevelDirInfo *node, int depth)
446 for (i=0; i<depth * 3; i++)
449 printf("filename == '%s'\n", node->filename);
451 if (node->node_group != NULL)
452 dumpLevelDirInfo(node->node_group, depth + 1);
458 void sortLevelDirInfo(struct LevelDirInfo **node_first,
459 int (*compare_function)(const void *, const void *))
461 int num_nodes = numLevelDirInfo(*node_first);
462 struct LevelDirInfo **sort_array;
463 struct LevelDirInfo *node = *node_first;
469 /* allocate array for sorting structure pointers */
470 sort_array = checked_calloc(num_nodes * sizeof(struct LevelDirInfo *));
472 /* writing structure pointers to sorting array */
473 while (i < num_nodes && node) /* double boundary check... */
475 sort_array[i] = node;
481 /* sorting the structure pointers in the sorting array */
482 qsort(sort_array, num_nodes, sizeof(struct LevelDirInfo *),
485 /* update the linkage of list elements with the sorted node array */
486 for (i=0; i<num_nodes - 1; i++)
487 sort_array[i]->next = sort_array[i + 1];
488 sort_array[num_nodes - 1]->next = NULL;
490 /* update the linkage of the main list anchor pointer */
491 *node_first = sort_array[0];
495 /* now recursively sort the level group structures */
499 if (node->node_group != NULL)
500 sortLevelDirInfo(&node->node_group, compare_function);
507 /* ========================================================================= */
508 /* some stuff from "files.c" */
509 /* ========================================================================= */
511 #if defined(PLATFORM_WIN32)
513 #define S_IRGRP S_IRUSR
516 #define S_IROTH S_IRUSR
519 #define S_IWGRP S_IWUSR
522 #define S_IWOTH S_IWUSR
525 #define S_IXGRP S_IXUSR
528 #define S_IXOTH S_IXUSR
531 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
536 #endif /* PLATFORM_WIN32 */
538 /* file permissions for newly written files */
539 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
540 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
541 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
543 #define MODE_W_PRIVATE (S_IWUSR)
544 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
545 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
547 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
548 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
550 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
551 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
553 char *getUserDataDir(void)
555 static char *userdata_dir = NULL;
559 char *home_dir = getHomeDir();
560 char *data_dir = program.userdata_directory;
562 userdata_dir = getPath2(home_dir, data_dir);
570 return getUserDataDir();
573 static mode_t posix_umask(mode_t mask)
575 #if defined(PLATFORM_UNIX)
582 static int posix_mkdir(const char *pathname, mode_t mode)
584 #if defined(PLATFORM_WIN32)
585 return mkdir(pathname);
587 return mkdir(pathname, mode);
591 void createDirectory(char *dir, char *text, int permission_class)
593 /* leave "other" permissions in umask untouched, but ensure group parts
594 of USERDATA_DIR_MODE are not masked */
595 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
596 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
597 mode_t normal_umask = posix_umask(0);
598 mode_t group_umask = ~(dir_mode & S_IRWXG);
599 posix_umask(normal_umask & group_umask);
601 if (access(dir, F_OK) != 0)
602 if (posix_mkdir(dir, dir_mode) != 0)
603 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
605 posix_umask(normal_umask); /* reset normal umask */
608 void InitUserDataDirectory()
610 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
613 void SetFilePermissions(char *filename, int permission_class)
615 chmod(filename, (permission_class == PERMS_PRIVATE ?
616 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
619 char *getCookie(char *file_type)
621 static char cookie[MAX_COOKIE_LEN + 1];
623 if (strlen(program.cookie_prefix) + 1 +
624 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
625 return "[COOKIE ERROR]"; /* should never happen */
627 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
628 program.cookie_prefix, file_type,
629 program.version_major, program.version_minor);
634 int getFileVersionFromCookieString(const char *cookie)
636 const char *ptr_cookie1, *ptr_cookie2;
637 const char *pattern1 = "_FILE_VERSION_";
638 const char *pattern2 = "?.?";
639 const int len_cookie = strlen(cookie);
640 const int len_pattern1 = strlen(pattern1);
641 const int len_pattern2 = strlen(pattern2);
642 const int len_pattern = len_pattern1 + len_pattern2;
643 int version_major, version_minor;
645 if (len_cookie <= len_pattern)
648 ptr_cookie1 = &cookie[len_cookie - len_pattern];
649 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
651 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
654 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
655 ptr_cookie2[1] != '.' ||
656 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
659 version_major = ptr_cookie2[0] - '0';
660 version_minor = ptr_cookie2[2] - '0';
662 return VERSION_IDENT(version_major, version_minor, 0);
665 boolean checkCookieString(const char *cookie, const char *template)
667 const char *pattern = "_FILE_VERSION_?.?";
668 const int len_cookie = strlen(cookie);
669 const int len_template = strlen(template);
670 const int len_pattern = strlen(pattern);
672 if (len_cookie != len_template)
675 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
681 /* ------------------------------------------------------------------------- */
682 /* setup file list handling functions */
683 /* ------------------------------------------------------------------------- */
685 int get_string_integer_value(char *s)
687 static char *number_text[][3] =
689 { "0", "zero", "null", },
690 { "1", "one", "first" },
691 { "2", "two", "second" },
692 { "3", "three", "third" },
693 { "4", "four", "fourth" },
694 { "5", "five", "fifth" },
695 { "6", "six", "sixth" },
696 { "7", "seven", "seventh" },
697 { "8", "eight", "eighth" },
698 { "9", "nine", "ninth" },
699 { "10", "ten", "tenth" },
700 { "11", "eleven", "eleventh" },
701 { "12", "twelve", "twelfth" },
705 char *s_lower = getStringToLower(s);
710 if (strcmp(s_lower, number_text[i][j]) == 0)
721 boolean get_string_boolean_value(char *s)
723 char *s_lower = getStringToLower(s);
724 boolean result = FALSE;
726 if (strcmp(s_lower, "true") == 0 ||
727 strcmp(s_lower, "yes") == 0 ||
728 strcmp(s_lower, "on") == 0 ||
729 get_string_integer_value(s) == 1)
737 char *getFormattedSetupEntry(char *token, char *value)
740 static char entry[MAX_LINE_LEN];
742 sprintf(entry, "%s:", token);
743 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
747 strcat(entry, value);
752 void freeSetupFileList(struct SetupFileList *setup_file_list)
754 if (!setup_file_list)
757 if (setup_file_list->token)
758 free(setup_file_list->token);
759 if (setup_file_list->value)
760 free(setup_file_list->value);
761 if (setup_file_list->next)
762 freeSetupFileList(setup_file_list->next);
763 free(setup_file_list);
766 static struct SetupFileList *newSetupFileList(char *token, char *value)
768 struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
770 new->token = checked_malloc(strlen(token) + 1);
771 strcpy(new->token, token);
773 new->value = checked_malloc(strlen(value) + 1);
774 strcpy(new->value, value);
781 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
783 if (!setup_file_list)
786 if (strcmp(setup_file_list->token, token) == 0)
787 return setup_file_list->value;
789 return getTokenValue(setup_file_list->next, token);
792 static void setTokenValue(struct SetupFileList *setup_file_list,
793 char *token, char *value)
795 if (!setup_file_list)
798 if (strcmp(setup_file_list->token, token) == 0)
800 free(setup_file_list->value);
801 setup_file_list->value = checked_malloc(strlen(value) + 1);
802 strcpy(setup_file_list->value, value);
804 else if (setup_file_list->next == NULL)
805 setup_file_list->next = newSetupFileList(token, value);
807 setTokenValue(setup_file_list->next, token, value);
811 static void printSetupFileList(struct SetupFileList *setup_file_list)
813 if (!setup_file_list)
816 printf("token: '%s'\n", setup_file_list->token);
817 printf("value: '%s'\n", setup_file_list->value);
819 printSetupFileList(setup_file_list->next);
823 struct SetupFileList *loadSetupFileList(char *filename)
826 char line[MAX_LINE_LEN];
827 char *token, *value, *line_ptr;
828 struct SetupFileList *setup_file_list = newSetupFileList("", "");
829 struct SetupFileList *first_valid_list_entry;
833 if (!(file = fopen(filename, MODE_READ)))
835 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
841 /* read next line of input file */
842 if (!fgets(line, MAX_LINE_LEN, file))
845 /* cut trailing comment or whitespace from input line */
846 for (line_ptr = line; *line_ptr; line_ptr++)
848 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
855 /* cut trailing whitespaces from input line */
856 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
857 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
860 /* ignore empty lines */
864 line_len = strlen(line);
866 /* cut leading whitespaces from token */
867 for (token = line; *token; token++)
868 if (*token != ' ' && *token != '\t')
871 /* find end of token */
872 for (line_ptr = token; *line_ptr; line_ptr++)
874 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
881 if (line_ptr < line + line_len)
882 value = line_ptr + 1;
886 /* cut leading whitespaces from value */
887 for (; *value; value++)
888 if (*value != ' ' && *value != '\t')
891 if (*token && *value)
892 setTokenValue(setup_file_list, token, value);
897 first_valid_list_entry = setup_file_list->next;
899 /* free empty list header */
900 setup_file_list->next = NULL;
901 freeSetupFileList(setup_file_list);
903 if (first_valid_list_entry == NULL)
904 Error(ERR_WARN, "configuration file '%s' is empty", filename);
906 return first_valid_list_entry;
909 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
912 if (!setup_file_list)
915 if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
917 if (!checkCookieString(setup_file_list->value, identifier))
919 Error(ERR_WARN, "configuration file has wrong file identifier");
926 if (setup_file_list->next)
927 checkSetupFileListIdentifier(setup_file_list->next, identifier);
930 Error(ERR_WARN, "configuration file has no file identifier");
936 /* ========================================================================= */
937 /* setup file stuff */
938 /* ========================================================================= */
940 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
941 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
942 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
944 /* level directory info */
945 #define LEVELINFO_TOKEN_NAME 0
946 #define LEVELINFO_TOKEN_NAME_SHORT 1
947 #define LEVELINFO_TOKEN_NAME_SORTING 2
948 #define LEVELINFO_TOKEN_AUTHOR 3
949 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
950 #define LEVELINFO_TOKEN_LEVELS 5
951 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
952 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
953 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
954 #define LEVELINFO_TOKEN_READONLY 9
956 #define NUM_LEVELINFO_TOKENS 10
958 static struct LevelDirInfo ldi;
960 static struct TokenInfo levelinfo_tokens[] =
962 /* level directory info */
963 { TYPE_STRING, &ldi.name, "name" },
964 { TYPE_STRING, &ldi.name_short, "name_short" },
965 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
966 { TYPE_STRING, &ldi.author, "author" },
967 { TYPE_STRING, &ldi.imported_from, "imported_from" },
968 { TYPE_INTEGER, &ldi.levels, "levels" },
969 { TYPE_INTEGER, &ldi.first_level, "first_level" },
970 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
971 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
972 { TYPE_BOOLEAN, &ldi.readonly, "readonly" }
975 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
977 ldi->filename = NULL;
978 ldi->fullpath = NULL;
979 ldi->basepath = NULL;
980 ldi->name = getStringCopy(ANONYMOUS_NAME);
981 ldi->name_short = NULL;
982 ldi->name_sorting = NULL;
983 ldi->author = getStringCopy(ANONYMOUS_NAME);
984 ldi->imported_from = NULL;
986 ldi->first_level = 0;
988 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
989 ldi->level_group = FALSE;
990 ldi->parent_link = FALSE;
991 ldi->user_defined = FALSE;
992 ldi->readonly = TRUE;
994 ldi->class_desc = NULL;
995 ldi->handicap_level = 0;
999 ldi->node_parent = NULL;
1000 ldi->node_group = NULL;
1004 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1005 struct LevelDirInfo *parent)
1009 setLevelDirInfoToDefaults(ldi);
1013 /* first copy all values from the parent structure ... */
1016 /* ... then set all fields to default that cannot be inherited from parent.
1017 This is especially important for all those fields that can be set from
1018 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1019 calls 'free()' for all already set token values which requires that no
1020 other structure's pointer may point to them!
1023 ldi->filename = NULL;
1024 ldi->fullpath = NULL;
1025 ldi->basepath = NULL;
1026 ldi->name = getStringCopy(ANONYMOUS_NAME);
1027 ldi->name_short = NULL;
1028 ldi->name_sorting = NULL;
1029 ldi->author = getStringCopy(parent->author);
1030 ldi->imported_from = getStringCopy(parent->imported_from);
1032 ldi->level_group = FALSE;
1033 ldi->parent_link = FALSE;
1035 ldi->node_parent = parent;
1036 ldi->node_group = NULL;
1040 void setSetupInfo(struct TokenInfo *token_info,
1041 int token_nr, char *token_value)
1043 int token_type = token_info[token_nr].type;
1044 void *setup_value = token_info[token_nr].value;
1046 if (token_value == NULL)
1049 /* set setup field to corresponding token value */
1054 *(boolean *)setup_value = get_string_boolean_value(token_value);
1058 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1062 *(int *)setup_value = get_string_integer_value(token_value);
1066 if (*(char **)setup_value != NULL)
1067 free(*(char **)setup_value);
1068 *(char **)setup_value = getStringCopy(token_value);
1076 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1078 const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1079 const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1082 if (entry1->parent_link || entry2->parent_link)
1083 compare_result = (entry1->parent_link ? -1 : +1);
1084 else if (entry1->sort_priority == entry2->sort_priority)
1086 char *name1 = getStringToLower(entry1->name_sorting);
1087 char *name2 = getStringToLower(entry2->name_sorting);
1089 compare_result = strcmp(name1, name2);
1094 else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1095 compare_result = entry1->sort_priority - entry2->sort_priority;
1097 compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1099 return compare_result;
1102 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1104 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1106 setLevelDirInfoToDefaults(leveldir_new);
1108 leveldir_new->node_parent = node_parent;
1109 leveldir_new->parent_link = TRUE;
1111 leveldir_new->name = ".. (parent directory)";
1112 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1113 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1115 leveldir_new->filename = "..";
1116 leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1118 leveldir_new->sort_priority = node_parent->sort_priority;
1119 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1121 pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1124 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1125 struct LevelDirInfo *node_parent,
1126 char *level_directory)
1129 struct dirent *dir_entry;
1130 boolean valid_entry_found = FALSE;
1132 if ((dir = opendir(level_directory)) == NULL)
1134 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1138 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1140 struct SetupFileList *setup_file_list = NULL;
1141 struct stat file_status;
1142 char *directory_name = dir_entry->d_name;
1143 char *directory_path = getPath2(level_directory, directory_name);
1144 char *filename = NULL;
1146 /* skip entries for current and parent directory */
1147 if (strcmp(directory_name, ".") == 0 ||
1148 strcmp(directory_name, "..") == 0)
1150 free(directory_path);
1154 /* find out if directory entry is itself a directory */
1155 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1156 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1158 free(directory_path);
1162 filename = getPath2(directory_path, LEVELINFO_FILENAME);
1163 setup_file_list = loadSetupFileList(filename);
1165 if (setup_file_list)
1167 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1170 checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1171 setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1173 /* set all structure fields according to the token/value pairs */
1174 ldi = *leveldir_new;
1175 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1176 setSetupInfo(levelinfo_tokens, i,
1177 getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1178 *leveldir_new = ldi;
1180 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1182 if (leveldir_new->name_short == NULL)
1183 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1185 if (leveldir_new->name_sorting == NULL)
1186 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1188 leveldir_new->filename = getStringCopy(directory_name);
1190 if (node_parent == NULL) /* top level group */
1192 leveldir_new->basepath = level_directory;
1193 leveldir_new->fullpath = leveldir_new->filename;
1195 else /* sub level group */
1197 leveldir_new->basepath = node_parent->basepath;
1198 leveldir_new->fullpath = getPath2(node_parent->fullpath,
1202 if (leveldir_new->levels < 1)
1203 leveldir_new->levels = 1;
1205 leveldir_new->last_level =
1206 leveldir_new->first_level + leveldir_new->levels - 1;
1208 leveldir_new->user_defined =
1209 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1211 leveldir_new->color = LEVELCOLOR(leveldir_new);
1212 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1214 leveldir_new->handicap_level = /* set handicap to default value */
1215 (leveldir_new->user_defined ?
1216 leveldir_new->last_level :
1217 leveldir_new->first_level);
1219 pushLevelDirInfo(node_first, leveldir_new);
1221 freeSetupFileList(setup_file_list);
1222 valid_entry_found = TRUE;
1224 if (leveldir_new->level_group)
1226 /* create node to link back to current level directory */
1227 createParentLevelDirNode(leveldir_new);
1229 /* step into sub-directory and look for more level series */
1230 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1231 leveldir_new, directory_path);
1235 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1237 free(directory_path);
1243 if (!valid_entry_found)
1244 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1248 void LoadLevelInfo()
1250 InitUserLevelDirectory(getLoginName());
1252 DrawInitText("Loading level series:", 120, FC_GREEN);
1254 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1255 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1257 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1259 if (leveldir_first == NULL)
1260 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1262 sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1265 dumpLevelDirInfo(leveldir_first, 0);
1269 static void SaveUserLevelInfo()
1275 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1277 if (!(file = fopen(filename, MODE_WRITE)))
1279 Error(ERR_WARN, "cannot write level info file '%s'", filename);
1284 /* always start with reliable default values */
1285 setLevelDirInfoToDefaults(&ldi);
1287 ldi.name = getLoginName();
1288 ldi.author = getRealName();
1290 ldi.first_level = 1;
1291 ldi.sort_priority = LEVELCLASS_USER_START;
1292 ldi.readonly = FALSE;
1294 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1295 getCookie("LEVELINFO")));
1297 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1298 if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1299 i != LEVELINFO_TOKEN_NAME_SORTING &&
1300 i != LEVELINFO_TOKEN_IMPORTED_FROM)
1301 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1306 SetFilePermissions(filename, PERMS_PRIVATE);
1309 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1312 static char entry[MAX_LINE_LEN];
1313 int token_type = token_info[token_nr].type;
1314 void *setup_value = token_info[token_nr].value;
1315 char *token_text = token_info[token_nr].text;
1317 /* start with the prefix, token and some spaces to format output line */
1318 sprintf(entry, "%s%s:", prefix, token_text);
1319 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1322 /* continue with the token's value (which can have different types) */
1326 strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1330 strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1335 Key key = *(Key *)setup_value;
1336 char *keyname = getKeyNameFromKey(key);
1338 strcat(entry, getX11KeyNameFromKey(key));
1339 for (i=strlen(entry); i<50; i++)
1342 /* add comment, if useful */
1343 if (strcmp(keyname, "(undefined)") != 0 &&
1344 strcmp(keyname, "(unknown)") != 0)
1346 strcat(entry, "# ");
1347 strcat(entry, keyname);
1354 char buffer[MAX_LINE_LEN];
1356 sprintf(buffer, "%d", *(int *)setup_value);
1357 strcat(entry, buffer);
1362 strcat(entry, *(char **)setup_value);
1372 void LoadLevelSetup_LastSeries()
1375 struct SetupFileList *level_setup_list = NULL;
1377 /* always start with reliable default values */
1378 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1380 /* ----------------------------------------------------------------------- */
1381 /* ~/.<program>/levelsetup.conf */
1382 /* ----------------------------------------------------------------------- */
1384 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1386 if ((level_setup_list = loadSetupFileList(filename)))
1388 char *last_level_series =
1389 getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1391 leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1392 if (leveldir_current == NULL)
1393 leveldir_current = leveldir_first;
1395 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1397 freeSetupFileList(level_setup_list);
1400 Error(ERR_WARN, "using default setup values");
1405 void SaveLevelSetup_LastSeries()
1408 char *level_subdir = leveldir_current->filename;
1411 /* ----------------------------------------------------------------------- */
1412 /* ~/.<program>/levelsetup.conf */
1413 /* ----------------------------------------------------------------------- */
1415 InitUserDataDirectory();
1417 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1419 if (!(file = fopen(filename, MODE_WRITE)))
1421 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1426 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1427 getCookie("LEVELSETUP")));
1428 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1434 SetFilePermissions(filename, PERMS_PRIVATE);
1437 static void checkSeriesInfo()
1439 static char *level_directory = NULL;
1441 struct dirent *dir_entry;
1443 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1445 level_directory = getPath2((leveldir_current->user_defined ?
1446 getUserLevelDir("") :
1447 options.level_directory),
1448 leveldir_current->fullpath);
1450 if ((dir = opendir(level_directory)) == NULL)
1452 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1456 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
1458 if (strlen(dir_entry->d_name) > 4 &&
1459 dir_entry->d_name[3] == '.' &&
1460 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1462 char levelnum_str[4];
1465 strncpy(levelnum_str, dir_entry->d_name, 3);
1466 levelnum_str[3] = '\0';
1468 levelnum_value = atoi(levelnum_str);
1470 if (levelnum_value < leveldir_current->first_level)
1472 Error(ERR_WARN, "additional level %d found", levelnum_value);
1473 leveldir_current->first_level = levelnum_value;
1475 else if (levelnum_value > leveldir_current->last_level)
1477 Error(ERR_WARN, "additional level %d found", levelnum_value);
1478 leveldir_current->last_level = levelnum_value;
1486 void LoadLevelSetup_SeriesInfo()
1489 struct SetupFileList *level_setup_list = NULL;
1490 char *level_subdir = leveldir_current->filename;
1492 /* always start with reliable default values */
1493 level_nr = leveldir_current->first_level;
1495 checkSeriesInfo(leveldir_current);
1497 /* ----------------------------------------------------------------------- */
1498 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1499 /* ----------------------------------------------------------------------- */
1501 level_subdir = leveldir_current->filename;
1503 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1505 if ((level_setup_list = loadSetupFileList(filename)))
1509 token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1513 level_nr = atoi(token_value);
1515 if (level_nr < leveldir_current->first_level)
1516 level_nr = leveldir_current->first_level;
1517 if (level_nr > leveldir_current->last_level)
1518 level_nr = leveldir_current->last_level;
1521 token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1525 int level_nr = atoi(token_value);
1527 if (level_nr < leveldir_current->first_level)
1528 level_nr = leveldir_current->first_level;
1529 if (level_nr > leveldir_current->last_level + 1)
1530 level_nr = leveldir_current->last_level;
1532 if (leveldir_current->user_defined)
1533 level_nr = leveldir_current->last_level;
1535 leveldir_current->handicap_level = level_nr;
1538 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1540 freeSetupFileList(level_setup_list);
1543 Error(ERR_WARN, "using default setup values");
1548 void SaveLevelSetup_SeriesInfo()
1551 char *level_subdir = leveldir_current->filename;
1552 char *level_nr_str = int2str(level_nr, 0);
1553 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1556 /* ----------------------------------------------------------------------- */
1557 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1558 /* ----------------------------------------------------------------------- */
1560 InitLevelSetupDirectory(level_subdir);
1562 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1564 if (!(file = fopen(filename, MODE_WRITE)))
1566 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1571 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1572 getCookie("LEVELSETUP")));
1573 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1575 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1576 handicap_level_str));
1581 SetFilePermissions(filename, PERMS_PRIVATE);