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 char *getCustomImageFilename(char *basename)
253 if (strcmp(basename, "RocksFont.pcx") == 0)
255 char *dir = options.graphics_directory;
257 printf("checking directory '%s' ...\n", dir);
260 dir = getPath2(options.graphics_directory);
265 return getImageFilename(basename);
268 void InitTapeDirectory(char *level_subdir)
270 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
271 createDirectory(getTapeDir(""), "main tape", PERMS_PRIVATE);
272 createDirectory(getTapeDir(level_subdir), "level tape", PERMS_PRIVATE);
275 void InitScoreDirectory(char *level_subdir)
277 createDirectory(getScoreDir(""), "main score", PERMS_PUBLIC);
278 createDirectory(getScoreDir(level_subdir), "level score", PERMS_PUBLIC);
281 static void SaveUserLevelInfo();
283 void InitUserLevelDirectory(char *level_subdir)
285 if (access(getUserLevelDir(level_subdir), F_OK) != 0)
287 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
288 createDirectory(getUserLevelDir(""), "main user level", PERMS_PRIVATE);
289 createDirectory(getUserLevelDir(level_subdir), "user level",PERMS_PRIVATE);
295 void InitLevelSetupDirectory(char *level_subdir)
297 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
298 createDirectory(getLevelSetupDir(""), "main level setup", PERMS_PRIVATE);
299 createDirectory(getLevelSetupDir(level_subdir), "level setup",PERMS_PRIVATE);
302 void ReadChunk_VERS(FILE *file, int *file_version, int *game_version)
304 int file_version_major, file_version_minor, file_version_patch;
305 int game_version_major, game_version_minor, game_version_patch;
307 file_version_major = fgetc(file);
308 file_version_minor = fgetc(file);
309 file_version_patch = fgetc(file);
310 fgetc(file); /* not used */
312 game_version_major = fgetc(file);
313 game_version_minor = fgetc(file);
314 game_version_patch = fgetc(file);
315 fgetc(file); /* not used */
317 *file_version = VERSION_IDENT(file_version_major,
321 *game_version = VERSION_IDENT(game_version_major,
326 void WriteChunk_VERS(FILE *file, int file_version, int game_version)
328 int file_version_major = VERSION_MAJOR(file_version);
329 int file_version_minor = VERSION_MINOR(file_version);
330 int file_version_patch = VERSION_PATCH(file_version);
331 int game_version_major = VERSION_MAJOR(game_version);
332 int game_version_minor = VERSION_MINOR(game_version);
333 int game_version_patch = VERSION_PATCH(game_version);
335 fputc(file_version_major, file);
336 fputc(file_version_minor, file);
337 fputc(file_version_patch, file);
338 fputc(0, file); /* not used */
340 fputc(game_version_major, file);
341 fputc(game_version_minor, file);
342 fputc(game_version_patch, file);
343 fputc(0, file); /* not used */
347 /* ------------------------------------------------------------------------- */
348 /* some functions to handle lists of level directories */
349 /* ------------------------------------------------------------------------- */
351 struct LevelDirInfo *newLevelDirInfo()
353 return checked_calloc(sizeof(struct LevelDirInfo));
356 void pushLevelDirInfo(struct LevelDirInfo **node_first,
357 struct LevelDirInfo *node_new)
359 node_new->next = *node_first;
360 *node_first = node_new;
363 int numLevelDirInfo(struct LevelDirInfo *node)
376 boolean validLevelSeries(struct LevelDirInfo *node)
378 return (node != NULL && !node->node_group && !node->parent_link);
381 struct LevelDirInfo *getFirstValidLevelSeries(struct LevelDirInfo *node)
385 if (leveldir_first) /* start with first level directory entry */
386 return getFirstValidLevelSeries(leveldir_first);
390 else if (node->node_group) /* enter level group (step down into tree) */
391 return getFirstValidLevelSeries(node->node_group);
392 else if (node->parent_link) /* skip start entry of level group */
394 if (node->next) /* get first real level series entry */
395 return getFirstValidLevelSeries(node->next);
396 else /* leave empty level group and go on */
397 return getFirstValidLevelSeries(node->node_parent->next);
399 else /* this seems to be a regular level series */
403 struct LevelDirInfo *getLevelDirInfoFirstGroupEntry(struct LevelDirInfo *node)
408 if (node->node_parent == NULL) /* top level group */
409 return leveldir_first;
410 else /* sub level group */
411 return node->node_parent->node_group;
414 int numLevelDirInfoInGroup(struct LevelDirInfo *node)
416 return numLevelDirInfo(getLevelDirInfoFirstGroupEntry(node));
419 int posLevelDirInfo(struct LevelDirInfo *node)
421 struct LevelDirInfo *node_cmp = getLevelDirInfoFirstGroupEntry(node);
426 if (node_cmp == node)
430 node_cmp = node_cmp->next;
436 struct LevelDirInfo *getLevelDirInfoFromPos(struct LevelDirInfo *node, int pos)
438 struct LevelDirInfo *node_default = node;
453 struct LevelDirInfo *getLevelDirInfoFromFilenameExt(struct LevelDirInfo *node,
456 if (filename == NULL)
461 if (node->node_group)
463 struct LevelDirInfo *node_group;
465 node_group = getLevelDirInfoFromFilenameExt(node->node_group, filename);
470 else if (!node->parent_link)
472 if (strcmp(filename, node->filename) == 0)
482 struct LevelDirInfo *getLevelDirInfoFromFilename(char *filename)
484 return getLevelDirInfoFromFilenameExt(leveldir_first, filename);
487 void dumpLevelDirInfo(struct LevelDirInfo *node, int depth)
493 for (i=0; i<depth * 3; i++)
496 printf("filename == '%s'\n", node->filename);
498 if (node->node_group != NULL)
499 dumpLevelDirInfo(node->node_group, depth + 1);
505 void sortLevelDirInfo(struct LevelDirInfo **node_first,
506 int (*compare_function)(const void *, const void *))
508 int num_nodes = numLevelDirInfo(*node_first);
509 struct LevelDirInfo **sort_array;
510 struct LevelDirInfo *node = *node_first;
516 /* allocate array for sorting structure pointers */
517 sort_array = checked_calloc(num_nodes * sizeof(struct LevelDirInfo *));
519 /* writing structure pointers to sorting array */
520 while (i < num_nodes && node) /* double boundary check... */
522 sort_array[i] = node;
528 /* sorting the structure pointers in the sorting array */
529 qsort(sort_array, num_nodes, sizeof(struct LevelDirInfo *),
532 /* update the linkage of list elements with the sorted node array */
533 for (i=0; i<num_nodes - 1; i++)
534 sort_array[i]->next = sort_array[i + 1];
535 sort_array[num_nodes - 1]->next = NULL;
537 /* update the linkage of the main list anchor pointer */
538 *node_first = sort_array[0];
542 /* now recursively sort the level group structures */
546 if (node->node_group != NULL)
547 sortLevelDirInfo(&node->node_group, compare_function);
554 /* ========================================================================= */
555 /* some stuff from "files.c" */
556 /* ========================================================================= */
558 #if defined(PLATFORM_WIN32)
560 #define S_IRGRP S_IRUSR
563 #define S_IROTH S_IRUSR
566 #define S_IWGRP S_IWUSR
569 #define S_IWOTH S_IWUSR
572 #define S_IXGRP S_IXUSR
575 #define S_IXOTH S_IXUSR
578 #define S_IRWXG (S_IRGRP | S_IWGRP | S_IXGRP)
583 #endif /* PLATFORM_WIN32 */
585 /* file permissions for newly written files */
586 #define MODE_R_ALL (S_IRUSR | S_IRGRP | S_IROTH)
587 #define MODE_W_ALL (S_IWUSR | S_IWGRP | S_IWOTH)
588 #define MODE_X_ALL (S_IXUSR | S_IXGRP | S_IXOTH)
590 #define MODE_W_PRIVATE (S_IWUSR)
591 #define MODE_W_PUBLIC (S_IWUSR | S_IWGRP)
592 #define MODE_W_PUBLIC_DIR (S_IWUSR | S_IWGRP | S_ISGID)
594 #define DIR_PERMS_PRIVATE (MODE_R_ALL | MODE_X_ALL | MODE_W_PRIVATE)
595 #define DIR_PERMS_PUBLIC (MODE_R_ALL | MODE_X_ALL | MODE_W_PUBLIC_DIR)
597 #define FILE_PERMS_PRIVATE (MODE_R_ALL | MODE_W_PRIVATE)
598 #define FILE_PERMS_PUBLIC (MODE_R_ALL | MODE_W_PUBLIC)
600 char *getUserDataDir(void)
602 static char *userdata_dir = NULL;
606 char *home_dir = getHomeDir();
607 char *data_dir = program.userdata_directory;
609 userdata_dir = getPath2(home_dir, data_dir);
617 return getUserDataDir();
620 static mode_t posix_umask(mode_t mask)
622 #if defined(PLATFORM_UNIX)
629 static int posix_mkdir(const char *pathname, mode_t mode)
631 #if defined(PLATFORM_WIN32)
632 return mkdir(pathname);
634 return mkdir(pathname, mode);
638 void createDirectory(char *dir, char *text, int permission_class)
640 /* leave "other" permissions in umask untouched, but ensure group parts
641 of USERDATA_DIR_MODE are not masked */
642 mode_t dir_mode = (permission_class == PERMS_PRIVATE ?
643 DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC);
644 mode_t normal_umask = posix_umask(0);
645 mode_t group_umask = ~(dir_mode & S_IRWXG);
646 posix_umask(normal_umask & group_umask);
648 if (access(dir, F_OK) != 0)
649 if (posix_mkdir(dir, dir_mode) != 0)
650 Error(ERR_WARN, "cannot create %s directory '%s'", text, dir);
652 posix_umask(normal_umask); /* reset normal umask */
655 void InitUserDataDirectory()
657 createDirectory(getUserDataDir(), "user data", PERMS_PRIVATE);
660 void SetFilePermissions(char *filename, int permission_class)
662 chmod(filename, (permission_class == PERMS_PRIVATE ?
663 FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC));
666 char *getCookie(char *file_type)
668 static char cookie[MAX_COOKIE_LEN + 1];
670 if (strlen(program.cookie_prefix) + 1 +
671 strlen(file_type) + strlen("_FILE_VERSION_x.x") > MAX_COOKIE_LEN)
672 return "[COOKIE ERROR]"; /* should never happen */
674 sprintf(cookie, "%s_%s_FILE_VERSION_%d.%d",
675 program.cookie_prefix, file_type,
676 program.version_major, program.version_minor);
681 int getFileVersionFromCookieString(const char *cookie)
683 const char *ptr_cookie1, *ptr_cookie2;
684 const char *pattern1 = "_FILE_VERSION_";
685 const char *pattern2 = "?.?";
686 const int len_cookie = strlen(cookie);
687 const int len_pattern1 = strlen(pattern1);
688 const int len_pattern2 = strlen(pattern2);
689 const int len_pattern = len_pattern1 + len_pattern2;
690 int version_major, version_minor;
692 if (len_cookie <= len_pattern)
695 ptr_cookie1 = &cookie[len_cookie - len_pattern];
696 ptr_cookie2 = &cookie[len_cookie - len_pattern2];
698 if (strncmp(ptr_cookie1, pattern1, len_pattern1) != 0)
701 if (ptr_cookie2[0] < '0' || ptr_cookie2[0] > '9' ||
702 ptr_cookie2[1] != '.' ||
703 ptr_cookie2[2] < '0' || ptr_cookie2[2] > '9')
706 version_major = ptr_cookie2[0] - '0';
707 version_minor = ptr_cookie2[2] - '0';
709 return VERSION_IDENT(version_major, version_minor, 0);
712 boolean checkCookieString(const char *cookie, const char *template)
714 const char *pattern = "_FILE_VERSION_?.?";
715 const int len_cookie = strlen(cookie);
716 const int len_template = strlen(template);
717 const int len_pattern = strlen(pattern);
719 if (len_cookie != len_template)
722 if (strncmp(cookie, template, len_cookie - len_pattern) != 0)
728 /* ------------------------------------------------------------------------- */
729 /* setup file list handling functions */
730 /* ------------------------------------------------------------------------- */
732 int get_string_integer_value(char *s)
734 static char *number_text[][3] =
736 { "0", "zero", "null", },
737 { "1", "one", "first" },
738 { "2", "two", "second" },
739 { "3", "three", "third" },
740 { "4", "four", "fourth" },
741 { "5", "five", "fifth" },
742 { "6", "six", "sixth" },
743 { "7", "seven", "seventh" },
744 { "8", "eight", "eighth" },
745 { "9", "nine", "ninth" },
746 { "10", "ten", "tenth" },
747 { "11", "eleven", "eleventh" },
748 { "12", "twelve", "twelfth" },
752 char *s_lower = getStringToLower(s);
757 if (strcmp(s_lower, number_text[i][j]) == 0)
768 boolean get_string_boolean_value(char *s)
770 char *s_lower = getStringToLower(s);
771 boolean result = FALSE;
773 if (strcmp(s_lower, "true") == 0 ||
774 strcmp(s_lower, "yes") == 0 ||
775 strcmp(s_lower, "on") == 0 ||
776 get_string_integer_value(s) == 1)
784 char *getFormattedSetupEntry(char *token, char *value)
787 static char entry[MAX_LINE_LEN];
789 sprintf(entry, "%s:", token);
790 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
794 strcat(entry, value);
799 void freeSetupFileList(struct SetupFileList *setup_file_list)
801 if (!setup_file_list)
804 if (setup_file_list->token)
805 free(setup_file_list->token);
806 if (setup_file_list->value)
807 free(setup_file_list->value);
808 if (setup_file_list->next)
809 freeSetupFileList(setup_file_list->next);
810 free(setup_file_list);
813 static struct SetupFileList *newSetupFileList(char *token, char *value)
815 struct SetupFileList *new = checked_malloc(sizeof(struct SetupFileList));
817 new->token = checked_malloc(strlen(token) + 1);
818 strcpy(new->token, token);
820 new->value = checked_malloc(strlen(value) + 1);
821 strcpy(new->value, value);
828 char *getTokenValue(struct SetupFileList *setup_file_list, char *token)
830 if (!setup_file_list)
833 if (strcmp(setup_file_list->token, token) == 0)
834 return setup_file_list->value;
836 return getTokenValue(setup_file_list->next, token);
839 static void setTokenValue(struct SetupFileList *setup_file_list,
840 char *token, char *value)
842 if (!setup_file_list)
845 if (strcmp(setup_file_list->token, token) == 0)
847 free(setup_file_list->value);
848 setup_file_list->value = checked_malloc(strlen(value) + 1);
849 strcpy(setup_file_list->value, value);
851 else if (setup_file_list->next == NULL)
852 setup_file_list->next = newSetupFileList(token, value);
854 setTokenValue(setup_file_list->next, token, value);
858 static void printSetupFileList(struct SetupFileList *setup_file_list)
860 if (!setup_file_list)
863 printf("token: '%s'\n", setup_file_list->token);
864 printf("value: '%s'\n", setup_file_list->value);
866 printSetupFileList(setup_file_list->next);
870 struct SetupFileList *loadSetupFileList(char *filename)
873 char line[MAX_LINE_LEN];
874 char *token, *value, *line_ptr;
875 struct SetupFileList *setup_file_list = newSetupFileList("", "");
876 struct SetupFileList *first_valid_list_entry;
880 if (!(file = fopen(filename, MODE_READ)))
882 Error(ERR_WARN, "cannot open configuration file '%s'", filename);
888 /* read next line of input file */
889 if (!fgets(line, MAX_LINE_LEN, file))
892 /* cut trailing comment or whitespace from input line */
893 for (line_ptr = line; *line_ptr; line_ptr++)
895 if (*line_ptr == '#' || *line_ptr == '\n' || *line_ptr == '\r')
902 /* cut trailing whitespaces from input line */
903 for (line_ptr = &line[strlen(line)]; line_ptr > line; line_ptr--)
904 if ((*line_ptr == ' ' || *line_ptr == '\t') && line_ptr[1] == '\0')
907 /* ignore empty lines */
911 line_len = strlen(line);
913 /* cut leading whitespaces from token */
914 for (token = line; *token; token++)
915 if (*token != ' ' && *token != '\t')
918 /* find end of token */
919 for (line_ptr = token; *line_ptr; line_ptr++)
921 if (*line_ptr == ' ' || *line_ptr == '\t' || *line_ptr == ':')
928 if (line_ptr < line + line_len)
929 value = line_ptr + 1;
933 /* cut leading whitespaces from value */
934 for (; *value; value++)
935 if (*value != ' ' && *value != '\t')
938 if (*token && *value)
939 setTokenValue(setup_file_list, token, value);
944 first_valid_list_entry = setup_file_list->next;
946 /* free empty list header */
947 setup_file_list->next = NULL;
948 freeSetupFileList(setup_file_list);
950 if (first_valid_list_entry == NULL)
951 Error(ERR_WARN, "configuration file '%s' is empty", filename);
953 return first_valid_list_entry;
956 void checkSetupFileListIdentifier(struct SetupFileList *setup_file_list,
959 if (!setup_file_list)
962 if (strcmp(setup_file_list->token, TOKEN_STR_FILE_IDENTIFIER) == 0)
964 if (!checkCookieString(setup_file_list->value, identifier))
966 Error(ERR_WARN, "configuration file has wrong file identifier");
973 if (setup_file_list->next)
974 checkSetupFileListIdentifier(setup_file_list->next, identifier);
977 Error(ERR_WARN, "configuration file has no file identifier");
983 /* ========================================================================= */
984 /* setup file stuff */
985 /* ========================================================================= */
987 #define TOKEN_STR_LAST_LEVEL_SERIES "last_level_series"
988 #define TOKEN_STR_LAST_PLAYED_LEVEL "last_played_level"
989 #define TOKEN_STR_HANDICAP_LEVEL "handicap_level"
991 /* level directory info */
992 #define LEVELINFO_TOKEN_NAME 0
993 #define LEVELINFO_TOKEN_NAME_SHORT 1
994 #define LEVELINFO_TOKEN_NAME_SORTING 2
995 #define LEVELINFO_TOKEN_AUTHOR 3
996 #define LEVELINFO_TOKEN_IMPORTED_FROM 4
997 #define LEVELINFO_TOKEN_LEVELS 5
998 #define LEVELINFO_TOKEN_FIRST_LEVEL 6
999 #define LEVELINFO_TOKEN_SORT_PRIORITY 7
1000 #define LEVELINFO_TOKEN_LEVEL_GROUP 8
1001 #define LEVELINFO_TOKEN_READONLY 9
1003 #define NUM_LEVELINFO_TOKENS 10
1005 static struct LevelDirInfo ldi;
1007 static struct TokenInfo levelinfo_tokens[] =
1009 /* level directory info */
1010 { TYPE_STRING, &ldi.name, "name" },
1011 { TYPE_STRING, &ldi.name_short, "name_short" },
1012 { TYPE_STRING, &ldi.name_sorting, "name_sorting" },
1013 { TYPE_STRING, &ldi.author, "author" },
1014 { TYPE_STRING, &ldi.imported_from, "imported_from" },
1015 { TYPE_INTEGER, &ldi.levels, "levels" },
1016 { TYPE_INTEGER, &ldi.first_level, "first_level" },
1017 { TYPE_INTEGER, &ldi.sort_priority, "sort_priority" },
1018 { TYPE_BOOLEAN, &ldi.level_group, "level_group" },
1019 { TYPE_BOOLEAN, &ldi.readonly, "readonly" }
1022 static void setLevelDirInfoToDefaults(struct LevelDirInfo *ldi)
1024 ldi->filename = NULL;
1025 ldi->fullpath = NULL;
1026 ldi->basepath = NULL;
1027 ldi->name = getStringCopy(ANONYMOUS_NAME);
1028 ldi->name_short = NULL;
1029 ldi->name_sorting = NULL;
1030 ldi->author = getStringCopy(ANONYMOUS_NAME);
1031 ldi->imported_from = NULL;
1033 ldi->first_level = 0;
1034 ldi->last_level = 0;
1035 ldi->sort_priority = LEVELCLASS_UNDEFINED; /* default: least priority */
1036 ldi->level_group = FALSE;
1037 ldi->parent_link = FALSE;
1038 ldi->user_defined = FALSE;
1039 ldi->readonly = TRUE;
1041 ldi->class_desc = NULL;
1042 ldi->handicap_level = 0;
1044 ldi->cl_cursor = -1;
1046 ldi->node_parent = NULL;
1047 ldi->node_group = NULL;
1051 static void setLevelDirInfoToDefaultsFromParent(struct LevelDirInfo *ldi,
1052 struct LevelDirInfo *parent)
1056 setLevelDirInfoToDefaults(ldi);
1060 /* first copy all values from the parent structure ... */
1063 /* ... then set all fields to default that cannot be inherited from parent.
1064 This is especially important for all those fields that can be set from
1065 the 'levelinfo.conf' config file, because the function 'setSetupInfo()'
1066 calls 'free()' for all already set token values which requires that no
1067 other structure's pointer may point to them!
1070 ldi->filename = NULL;
1071 ldi->fullpath = NULL;
1072 ldi->basepath = NULL;
1073 ldi->name = getStringCopy(ANONYMOUS_NAME);
1074 ldi->name_short = NULL;
1075 ldi->name_sorting = NULL;
1076 ldi->author = getStringCopy(parent->author);
1077 ldi->imported_from = getStringCopy(parent->imported_from);
1079 ldi->level_group = FALSE;
1080 ldi->parent_link = FALSE;
1082 ldi->node_parent = parent;
1083 ldi->node_group = NULL;
1087 void setSetupInfo(struct TokenInfo *token_info,
1088 int token_nr, char *token_value)
1090 int token_type = token_info[token_nr].type;
1091 void *setup_value = token_info[token_nr].value;
1093 if (token_value == NULL)
1096 /* set setup field to corresponding token value */
1101 *(boolean *)setup_value = get_string_boolean_value(token_value);
1105 *(Key *)setup_value = getKeyFromX11KeyName(token_value);
1109 *(int *)setup_value = get_string_integer_value(token_value);
1113 if (*(char **)setup_value != NULL)
1114 free(*(char **)setup_value);
1115 *(char **)setup_value = getStringCopy(token_value);
1123 static int compareLevelDirInfoEntries(const void *object1, const void *object2)
1125 const struct LevelDirInfo *entry1 = *((struct LevelDirInfo **)object1);
1126 const struct LevelDirInfo *entry2 = *((struct LevelDirInfo **)object2);
1129 if (entry1->parent_link || entry2->parent_link)
1130 compare_result = (entry1->parent_link ? -1 : +1);
1131 else if (entry1->sort_priority == entry2->sort_priority)
1133 char *name1 = getStringToLower(entry1->name_sorting);
1134 char *name2 = getStringToLower(entry2->name_sorting);
1136 compare_result = strcmp(name1, name2);
1141 else if (LEVELSORTING(entry1) == LEVELSORTING(entry2))
1142 compare_result = entry1->sort_priority - entry2->sort_priority;
1144 compare_result = LEVELSORTING(entry1) - LEVELSORTING(entry2);
1146 return compare_result;
1149 static void createParentLevelDirNode(struct LevelDirInfo *node_parent)
1151 struct LevelDirInfo *leveldir_new = newLevelDirInfo();
1153 setLevelDirInfoToDefaults(leveldir_new);
1155 leveldir_new->node_parent = node_parent;
1156 leveldir_new->parent_link = TRUE;
1158 leveldir_new->name = ".. (parent directory)";
1159 leveldir_new->name_short = getStringCopy(leveldir_new->name);
1160 leveldir_new->name_sorting = getStringCopy(leveldir_new->name);
1162 leveldir_new->filename = "..";
1163 leveldir_new->fullpath = getStringCopy(node_parent->fullpath);
1165 leveldir_new->sort_priority = node_parent->sort_priority;
1166 leveldir_new->class_desc = getLevelClassDescription(leveldir_new);
1168 pushLevelDirInfo(&node_parent->node_group, leveldir_new);
1171 /* forward declaration for recursive call by "LoadLevelInfoFromSetupFile()" */
1172 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **,
1173 struct LevelDirInfo *,
1176 static boolean LoadLevelInfoFromLevelDir(struct LevelDirInfo **node_first,
1177 struct LevelDirInfo *node_parent,
1178 char *level_directory,
1179 char *directory_name)
1181 char *directory_path = getPath2(level_directory, directory_name);
1182 char *filename = getPath2(directory_path, LEVELINFO_FILENAME);
1183 struct SetupFileList *setup_file_list = loadSetupFileList(filename);
1184 struct LevelDirInfo *leveldir_new = NULL;
1187 if (setup_file_list == NULL)
1189 Error(ERR_WARN, "ignoring level directory '%s'", level_directory);
1191 free(directory_path);
1197 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);
1252 if (leveldir_new->level_group)
1254 /* create node to link back to current level directory */
1255 createParentLevelDirNode(leveldir_new);
1257 /* step into sub-directory and look for more level series */
1258 LoadLevelInfoFromLevelGroupDir(&leveldir_new->node_group,
1259 leveldir_new, directory_path);
1262 free(directory_path);
1268 static void LoadLevelInfoFromLevelGroupDir(struct LevelDirInfo **node_first,
1269 struct LevelDirInfo *node_parent,
1270 char *level_directory)
1273 struct dirent *dir_entry;
1274 boolean valid_entry_found = FALSE;
1276 if ((dir = opendir(level_directory)) == NULL)
1278 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1282 while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */
1284 struct stat file_status;
1285 char *directory_name = dir_entry->d_name;
1286 char *directory_path = getPath2(level_directory, directory_name);
1288 /* skip entries for current and parent directory */
1289 if (strcmp(directory_name, ".") == 0 ||
1290 strcmp(directory_name, "..") == 0)
1292 free(directory_path);
1296 /* find out if directory entry is itself a directory */
1297 if (stat(directory_path, &file_status) != 0 || /* cannot stat file */
1298 (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */
1300 free(directory_path);
1304 free(directory_path);
1306 valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1313 if (!valid_entry_found)
1315 /* check if this directory directly contains a file "levelinfo.conf" */
1316 valid_entry_found |= LoadLevelInfoFromLevelDir(node_first, node_parent,
1317 level_directory, ".");
1320 if (!valid_entry_found)
1321 Error(ERR_WARN, "cannot find any valid level series in directory '%s'",
1325 void LoadLevelInfo()
1327 InitUserLevelDirectory(getLoginName());
1329 DrawInitText("Loading level series:", 120, FC_GREEN);
1331 /* check if this directory directly contains a file "levelinfo.conf" */
1332 LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,options.level_directory);
1333 LoadLevelInfoFromLevelGroupDir(&leveldir_first,NULL,getUserLevelDir(""));
1335 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1337 if (leveldir_first == NULL)
1338 Error(ERR_EXIT, "cannot find any valid level series in any directory");
1340 sortLevelDirInfo(&leveldir_first, compareLevelDirInfoEntries);
1343 dumpLevelDirInfo(leveldir_first, 0);
1347 static void SaveUserLevelInfo()
1353 filename = getPath2(getUserLevelDir(getLoginName()), LEVELINFO_FILENAME);
1355 if (!(file = fopen(filename, MODE_WRITE)))
1357 Error(ERR_WARN, "cannot write level info file '%s'", filename);
1362 /* always start with reliable default values */
1363 setLevelDirInfoToDefaults(&ldi);
1365 ldi.name = getLoginName();
1366 ldi.author = getRealName();
1368 ldi.first_level = 1;
1369 ldi.sort_priority = LEVELCLASS_USER_START;
1370 ldi.readonly = FALSE;
1372 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1373 getCookie("LEVELINFO")));
1375 for (i=0; i<NUM_LEVELINFO_TOKENS; i++)
1376 if (i != LEVELINFO_TOKEN_NAME_SHORT &&
1377 i != LEVELINFO_TOKEN_NAME_SORTING &&
1378 i != LEVELINFO_TOKEN_IMPORTED_FROM)
1379 fprintf(file, "%s\n", getSetupLine(levelinfo_tokens, "", i));
1384 SetFilePermissions(filename, PERMS_PRIVATE);
1387 char *getSetupValue(int type, void *value)
1389 static char value_string[MAX_LINE_LEN];
1394 strcpy(value_string, (*(boolean *)value ? "true" : "false"));
1398 strcpy(value_string, (*(boolean *)value ? "on" : "off"));
1402 strcpy(value_string, (*(boolean *)value ? "yes" : "no"));
1406 strcpy(value_string, getX11KeyNameFromKey(*(Key *)value));
1410 sprintf(value_string, "%d", *(int *)value);
1414 strcpy(value_string, *(char **)value);
1418 value_string[0] = '\0';
1422 return value_string;
1425 char *getSetupLine(struct TokenInfo *token_info, char *prefix, int token_nr)
1428 static char entry[MAX_LINE_LEN];
1429 int token_type = token_info[token_nr].type;
1430 void *setup_value = token_info[token_nr].value;
1431 char *token_text = token_info[token_nr].text;
1432 char *value_string = getSetupValue(token_type, setup_value);
1434 /* start with the prefix, token and some spaces to format output line */
1435 sprintf(entry, "%s%s:", prefix, token_text);
1436 for (i=strlen(entry); i<TOKEN_VALUE_POSITION; i++)
1439 /* continue with the token's value (which can have different types) */
1440 strcat(entry, value_string);
1442 if (token_type == TYPE_KEY)
1444 Key key = *(Key *)setup_value;
1445 char *keyname = getKeyNameFromKey(key);
1447 /* add comment, if useful */
1448 if (strcmp(keyname, "(undefined)") != 0 &&
1449 strcmp(keyname, "(unknown)") != 0)
1451 for (i=strlen(entry); i<50; i++)
1454 strcat(entry, "# ");
1455 strcat(entry, keyname);
1462 void LoadLevelSetup_LastSeries()
1465 struct SetupFileList *level_setup_list = NULL;
1467 /* always start with reliable default values */
1468 leveldir_current = getFirstValidLevelSeries(leveldir_first);
1470 /* ----------------------------------------------------------------------- */
1471 /* ~/.<program>/levelsetup.conf */
1472 /* ----------------------------------------------------------------------- */
1474 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1476 if ((level_setup_list = loadSetupFileList(filename)))
1478 char *last_level_series =
1479 getTokenValue(level_setup_list, TOKEN_STR_LAST_LEVEL_SERIES);
1481 leveldir_current = getLevelDirInfoFromFilename(last_level_series);
1482 if (leveldir_current == NULL)
1483 leveldir_current = leveldir_first;
1485 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1487 freeSetupFileList(level_setup_list);
1490 Error(ERR_WARN, "using default setup values");
1495 void SaveLevelSetup_LastSeries()
1498 char *level_subdir = leveldir_current->filename;
1501 /* ----------------------------------------------------------------------- */
1502 /* ~/.<program>/levelsetup.conf */
1503 /* ----------------------------------------------------------------------- */
1505 InitUserDataDirectory();
1507 filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME);
1509 if (!(file = fopen(filename, MODE_WRITE)))
1511 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1516 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1517 getCookie("LEVELSETUP")));
1518 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES,
1524 SetFilePermissions(filename, PERMS_PRIVATE);
1527 static void checkSeriesInfo()
1529 static char *level_directory = NULL;
1531 struct dirent *dir_entry;
1533 /* check for more levels besides the 'levels' field of 'levelinfo.conf' */
1535 level_directory = getPath2((leveldir_current->user_defined ?
1536 getUserLevelDir("") :
1537 options.level_directory),
1538 leveldir_current->fullpath);
1540 if ((dir = opendir(level_directory)) == NULL)
1542 Error(ERR_WARN, "cannot read level directory '%s'", level_directory);
1546 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */
1548 if (strlen(dir_entry->d_name) > 4 &&
1549 dir_entry->d_name[3] == '.' &&
1550 strcmp(&dir_entry->d_name[4], LEVELFILE_EXTENSION) == 0)
1552 char levelnum_str[4];
1555 strncpy(levelnum_str, dir_entry->d_name, 3);
1556 levelnum_str[3] = '\0';
1558 levelnum_value = atoi(levelnum_str);
1560 if (levelnum_value < leveldir_current->first_level)
1562 Error(ERR_WARN, "additional level %d found", levelnum_value);
1563 leveldir_current->first_level = levelnum_value;
1565 else if (levelnum_value > leveldir_current->last_level)
1567 Error(ERR_WARN, "additional level %d found", levelnum_value);
1568 leveldir_current->last_level = levelnum_value;
1576 void LoadLevelSetup_SeriesInfo()
1579 struct SetupFileList *level_setup_list = NULL;
1580 char *level_subdir = leveldir_current->filename;
1582 /* always start with reliable default values */
1583 level_nr = leveldir_current->first_level;
1585 checkSeriesInfo(leveldir_current);
1587 /* ----------------------------------------------------------------------- */
1588 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1589 /* ----------------------------------------------------------------------- */
1591 level_subdir = leveldir_current->filename;
1593 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1595 if ((level_setup_list = loadSetupFileList(filename)))
1599 token_value = getTokenValue(level_setup_list, TOKEN_STR_LAST_PLAYED_LEVEL);
1603 level_nr = atoi(token_value);
1605 if (level_nr < leveldir_current->first_level)
1606 level_nr = leveldir_current->first_level;
1607 if (level_nr > leveldir_current->last_level)
1608 level_nr = leveldir_current->last_level;
1611 token_value = getTokenValue(level_setup_list, TOKEN_STR_HANDICAP_LEVEL);
1615 int level_nr = atoi(token_value);
1617 if (level_nr < leveldir_current->first_level)
1618 level_nr = leveldir_current->first_level;
1619 if (level_nr > leveldir_current->last_level + 1)
1620 level_nr = leveldir_current->last_level;
1622 if (leveldir_current->user_defined)
1623 level_nr = leveldir_current->last_level;
1625 leveldir_current->handicap_level = level_nr;
1628 checkSetupFileListIdentifier(level_setup_list, getCookie("LEVELSETUP"));
1630 freeSetupFileList(level_setup_list);
1633 Error(ERR_WARN, "using default setup values");
1638 void SaveLevelSetup_SeriesInfo()
1641 char *level_subdir = leveldir_current->filename;
1642 char *level_nr_str = int2str(level_nr, 0);
1643 char *handicap_level_str = int2str(leveldir_current->handicap_level, 0);
1646 /* ----------------------------------------------------------------------- */
1647 /* ~/.<program>/levelsetup/<level series>/levelsetup.conf */
1648 /* ----------------------------------------------------------------------- */
1650 InitLevelSetupDirectory(level_subdir);
1652 filename = getPath2(getLevelSetupDir(level_subdir), LEVELSETUP_FILENAME);
1654 if (!(file = fopen(filename, MODE_WRITE)))
1656 Error(ERR_WARN, "cannot write setup file '%s'", filename);
1661 fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER,
1662 getCookie("LEVELSETUP")));
1663 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL,
1665 fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL,
1666 handicap_level_str));
1671 SetFilePermissions(filename, PERMS_PRIVATE);