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 static void LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1154 struct LevelDirInfo *node_parent,
1155 char *level_directory)
1158 struct dirent *dir_entry;
1159 boolean valid_entry_found = FALSE;
1161 if ((dir = opendir(level_directory)) == NULL)
1163 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1167 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1169 struct SetupFileList *setup_file_list = NULL;
1170 struct stat file_status;
1171 char *directory_name = dir_entry->d_name;
1172 char *directory_path = getPath2(level_directory, directory_name);
1173 char *filename = NULL;
1175 /* skip entries for current and parent directory */
1176 if (strcmp(directory_name, ".") == 0 ||
1177 strcmp(directory_name, "..") == 0)
1179 free(directory_path);
1183 /* find out if directory entry is itself a directory */
1184 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1185 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1187 free(directory_path);
1191 filename = getPath2(directory_path, LEVELINFO_FILENAME);
1192 setup_file_list = loadSetupFileList(filename);
1194 if (setup_file_list)
1196 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1199 checkSetupFileListIdentifier(setup_file_list, getCookie("LEVELINFO"));
1200 setLevelDirInfoToDefaultsFromParent(leveldir_new, node_parent);
1202 /* set all structure fields according to the token/value pairs */
1203 ldi = *leveldir_new;
1204 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1205 setSetupInfo(levelinfo_tokens, i,
1206 getTokenValue(setup_file_list, levelinfo_tokens[i].text));
1207 *leveldir_new = ldi;
1209 DrawInitText(leveldir_new->name, 150, FC_YELLOW);
1211 if (leveldir_new->name_short == NULL)
1212 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1214 if (leveldir_new->name_sorting == NULL)
1215 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1217 leveldir_new->filename = getStringCopy(directory_name);
1219 if (node_parent == NULL) /* top level group */
1221 leveldir_new->basepath = level_directory;
1222 leveldir_new->fullpath = leveldir_new->filename;
1224 else /* sub level group */
1226 leveldir_new->basepath = node_parent->basepath;
1227 leveldir_new->fullpath = getPath2(node_parent->fullpath,
1231 if (leveldir_new->levels < 1)
1232 leveldir_new->levels = 1;
1234 leveldir_new->last_level =
1235 leveldir_new->first_level + leveldir_new->levels - 1;
1237 leveldir_new->user_defined =
1238 (leveldir_new->basepath == options.level_directory ? FALSE : TRUE);
1240 leveldir_new->color = LEVELCOLOR(leveldir_new);
1241 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1243 leveldir_new->handicap_level = /* set handicap to default value */
1244 (leveldir_new->user_defined ?
1245 leveldir_new->last_level :
1246 leveldir_new->first_level);
1248 pushLevelDirInfo(node_first, leveldir_new);
1250 freeSetupFileList(setup_file_list);
1251 valid_entry_found = TRUE;
1253 if (leveldir_new->level_group)
1255 /* create node to link back to current level directory */
1256 createParentLevelDirNode(leveldir_new);
1258 /* step into sub-directory and look for more level series */
1259 LoadLevelInfoFromLevelDir(&leveldir_new->node_group,
1260 leveldir_new, directory_path);
1264 Error(ERR_WARN, "ignoring level directory '%s'", directory_path);
1266 free(directory_path);
1272 if (!valid_entry_found)
1273 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1277 void LoadLevelInfo()
1279 InitUserLevelDirectory(getLoginName());
1281 DrawInitText("Loading level series:", 120, FC_GREEN);
1283 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, options.level_directory);
1284 LoadLevelInfoFromLevelDir(&leveldir_first, NULL, getUserLevelDir(""));
1286 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1288 if (leveldir_first == NULL)
1289 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1291 sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1294 dumpLevelDirInfo(leveldir_first, 0);
1298 static void SaveUserLevelInfo()
1304 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1306 if (!(file = fopen(filename, MODE_WRITE)))
1308 Error(ERR_WARN, "cannot write level info file '%s'", filename);
1313 /* always start with reliable default values */
1314 setLevelDirInfoToDefaults(&ldi);
1316 ldi.name = getLoginName();
1317 ldi.author = getRealName();
1319 ldi.first_level = 1;
1320 ldi.sort_priority = LEVELCLASS_USER_START;
1321 ldi.readonly = FALSE;
1323 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1324 getCookie("LEVELINFO")));
1326 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1327 if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1328 i != LEVELINFO_TOKEN_NAME_SORTING &&
1329 i != LEVELINFO_TOKEN_IMPORTED_FROM)
1330 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1335 SetFilePermissions(filename, PERMS_PRIVATE);
1338 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1341 static char entry[MAX_LINE_LEN];
1342 int token_type = token_info[token_nr].type;
1343 void *setup_value = token_info[token_nr].value;
1344 char *token_text = token_info[token_nr].text;
1346 /* start with the prefix, token and some spaces to format output line */
1347 sprintf(entry, "%s%s:", prefix, token_text);
1348 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1351 /* continue with the token's value (which can have different types) */
1355 strcat(entry, (*(boolean *)setup_value ? "true" : "false"));
1359 strcat(entry, (*(boolean *)setup_value ? "on" : "off"));
1364 Key key = *(Key *)setup_value;
1365 char *keyname = getKeyNameFromKey(key);
1367 strcat(entry, getX11KeyNameFromKey(key));
1368 for (i=strlen(entry); i<50; i++)
1371 /* add comment, if useful */
1372 if (strcmp(keyname, "(undefined)") != 0 &&
1373 strcmp(keyname, "(unknown)") != 0)
1375 strcat(entry, "# ");
1376 strcat(entry, keyname);
1383 char buffer[MAX_LINE_LEN];
1385 sprintf(buffer, "%d", *(int *)setup_value);
1386 strcat(entry, buffer);
1391 strcat(entry, *(char **)setup_value);
1401 void LoadLevelSetup_LastSeries()
1404 struct SetupFileList *level_setup_list = NULL;
1406 /* always start with reliable default values */
1407 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1409 /* ----------------------------------------------------------------------- */
1410 /* ~/.<program>/levelsetup.conf */
1411 /* ----------------------------------------------------------------------- */
1413 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1415 if ((level_setup_list = loadSetupFileList(filename)))
1417 char *last_level_series =
1418 getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1420 leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1421 if (leveldir_current == NULL)
1422 leveldir_current = leveldir_first;
1424 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1426 freeSetupFileList(level_setup_list);
1429 Error(ERR_WARN, "using default setup values");
1434 void SaveLevelSetup_LastSeries()
1437 char *level_subdir = leveldir_current->filename;
1440 /* ----------------------------------------------------------------------- */
1441 /* ~/.<program>/levelsetup.conf */
1442 /* ----------------------------------------------------------------------- */
1444 InitUserDataDirectory();
1446 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1448 if (!(file = fopen(filename, MODE_WRITE)))
1450 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1455 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1456 getCookie("LEVELSETUP")));
1457 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1463 SetFilePermissions(filename, PERMS_PRIVATE);
1466 static void checkSeriesInfo()
1468 static char *level_directory = NULL;
1470 struct dirent *dir_entry;
1472 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1474 level_directory = getPath2((leveldir_current->user_defined ?
1475 getUserLevelDir("") :
1476 options.level_directory),
1477 leveldir_current->fullpath);
1479 if ((dir = opendir(level_directory)) == NULL)
1481 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1485 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
1487 if (strlen(dir_entry->d_name) > 4 &&
1488 dir_entry->d_name[3] == '.' &&
1489 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1491 char levelnum_str[4];
1494 strncpy(levelnum_str, dir_entry->d_name, 3);
1495 levelnum_str[3] = '\0';
1497 levelnum_value = atoi(levelnum_str);
1499 if (levelnum_value < leveldir_current->first_level)
1501 Error(ERR_WARN, "additional level %d found", levelnum_value);
1502 leveldir_current->first_level = levelnum_value;
1504 else if (levelnum_value > leveldir_current->last_level)
1506 Error(ERR_WARN, "additional level %d found", levelnum_value);
1507 leveldir_current->last_level = levelnum_value;
1515 void LoadLevelSetup_SeriesInfo()
1518 struct SetupFileList *level_setup_list = NULL;
1519 char *level_subdir = leveldir_current->filename;
1521 /* always start with reliable default values */
1522 level_nr = leveldir_current->first_level;
1524 checkSeriesInfo(leveldir_current);
1526 /* ----------------------------------------------------------------------- */
1527 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1528 /* ----------------------------------------------------------------------- */
1530 level_subdir = leveldir_current->filename;
1532 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1534 if ((level_setup_list = loadSetupFileList(filename)))
1538 token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1542 level_nr = atoi(token_value);
1544 if (level_nr < leveldir_current->first_level)
1545 level_nr = leveldir_current->first_level;
1546 if (level_nr > leveldir_current->last_level)
1547 level_nr = leveldir_current->last_level;
1550 token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1554 int level_nr = atoi(token_value);
1556 if (level_nr < leveldir_current->first_level)
1557 level_nr = leveldir_current->first_level;
1558 if (level_nr > leveldir_current->last_level + 1)
1559 level_nr = leveldir_current->last_level;
1561 if (leveldir_current->user_defined)
1562 level_nr = leveldir_current->last_level;
1564 leveldir_current->handicap_level = level_nr;
1567 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1569 freeSetupFileList(level_setup_list);
1572 Error(ERR_WARN, "using default setup values");
1577 void SaveLevelSetup_SeriesInfo()
1580 char *level_subdir = leveldir_current->filename;
1581 char *level_nr_str = int2str(level_nr, 0);
1582 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1585 /* ----------------------------------------------------------------------- */
1586 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1587 /* ----------------------------------------------------------------------- */
1589 InitLevelSetupDirectory(level_subdir);
1591 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1593 if (!(file = fopen(filename, MODE_WRITE)))
1595 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1600 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1601 getCookie("LEVELSETUP")));
1602 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1604 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1605 handicap_level_str));
1610 SetFilePermissions(filename, PERMS_PRIVATE);