X-Git-Url: https://git.artsoft.org/?a=blobdiff_plain;f=src%2Flibgame%2Fsetup.c;h=f7cd1400171ee608cab6bccbc708ddbcc3dff132;hb=ccbc62287e20cd3776b95980a77be3fee5ad7053;hp=3e5392498dbdb63474df4b3e75b19c22488452d5;hpb=401f78b66d66488fe465945bef36b10faa6b55ef;p=rocksndiamonds.git diff --git a/src/libgame/setup.c b/src/libgame/setup.c index 3e539249..f7cd1400 100644 --- a/src/libgame/setup.c +++ b/src/libgame/setup.c @@ -16,6 +16,7 @@ #include #include #include +#include #include "platform.h" @@ -412,6 +413,19 @@ char *getSolutionTapeFilename(int nr) sprintf(basename, "%03d.%s", nr, TAPEFILE_EXTENSION); filename = getPath2(getSolutionTapeDir(), basename); + if (!fileExists(filename)) + { + static char *filename_sln = NULL; + + checked_free(filename_sln); + + sprintf(basename, "%03d.sln", nr); + filename_sln = getPath2(getSolutionTapeDir(), basename); + + if (fileExists(filename_sln)) + return filename_sln; + } + return filename; } @@ -1171,8 +1185,13 @@ void dumpTreeInfo(TreeInfo *node, int depth) for (i = 0; i < (depth + 1) * 3; i++) printf(" "); + printf("'%s' / '%s'\n", node->identifier, node->name); + + /* + // use for dumping artwork info tree printf("subdir == '%s' ['%s', '%s'] [%d])\n", node->subdir, node->fullpath, node->basepath, node->in_user_dir); + */ if (node->node_group != NULL) dumpTreeInfo(node->node_group, depth + 1); @@ -1410,21 +1429,42 @@ static int posix_mkdir(const char *pathname, mode_t mode) #endif } +static boolean posix_process_running_setgid() +{ +#if defined(PLATFORM_UNIX) + return (getgid() != getegid()); +#else + return FALSE; +#endif +} + void createDirectory(char *dir, char *text, int permission_class) { /* leave "other" permissions in umask untouched, but ensure group parts of USERDATA_DIR_MODE are not masked */ mode_t dir_mode = (permission_class == PERMS_PRIVATE ? DIR_PERMS_PRIVATE : DIR_PERMS_PUBLIC); - mode_t normal_umask = posix_umask(0); + mode_t last_umask = posix_umask(0); mode_t group_umask = ~(dir_mode & S_IRWXG); - posix_umask(normal_umask & group_umask); + int running_setgid = posix_process_running_setgid(); + + /* if we're setgid, protect files against "other" */ + /* else keep umask(0) to make the dir world-writable */ + + if (running_setgid) + posix_umask(last_umask & group_umask); + else + dir_mode |= MODE_W_ALL; if (!fileExists(dir)) if (posix_mkdir(dir, dir_mode) != 0) - Error(ERR_WARN, "cannot create %s directory '%s'", text, dir); + Error(ERR_WARN, "cannot create %s directory '%s': %s", + text, dir, strerror(errno)); - posix_umask(normal_umask); /* reset normal umask */ + if (permission_class == PERMS_PUBLIC && !running_setgid) + chmod(dir, dir_mode); + + posix_umask(last_umask); /* restore previous umask */ } void InitUserDataDirectory() @@ -1434,8 +1474,14 @@ void InitUserDataDirectory() void SetFilePermissions(char *filename, int permission_class) { - chmod(filename, (permission_class == PERMS_PRIVATE ? - FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC)); + int running_setgid = posix_process_running_setgid(); + int perms = (permission_class == PERMS_PRIVATE ? + FILE_PERMS_PRIVATE : FILE_PERMS_PUBLIC); + + if (permission_class == PERMS_PUBLIC && !running_setgid) + perms |= MODE_W_ALL; + + chmod(filename, perms); } char *getCookie(char *file_type) @@ -1591,6 +1637,7 @@ SetupFileList *addListEntry(SetupFileList *list, char *token, char *value) return addListEntry(list->next, token, value); } +#if 0 #ifdef DEBUG static void printSetupFileList(SetupFileList *list) { @@ -1603,6 +1650,7 @@ static void printSetupFileList(SetupFileList *list) printSetupFileList(list->next); } #endif +#endif #ifdef DEBUG DEFINE_HASHTABLE_INSERT(insert_hash_entry, char, char); @@ -1616,7 +1664,7 @@ DEFINE_HASHTABLE_REMOVE(remove_hash_entry, char, char); #define remove_hash_entry hashtable_remove #endif -static unsigned int get_hash_from_key(void *key) +unsigned int get_hash_from_key(void *key) { /* djb2 @@ -1870,6 +1918,196 @@ boolean getTokenValueFromSetupLine(char *line, char **token, char **value) } #if 1 + +#if 1 +static boolean loadSetupFileData(void *setup_file_data, char *filename, + boolean top_recursion_level, boolean is_hash) +{ + static SetupFileHash *include_filename_hash = NULL; + char line[MAX_LINE_LEN], line_raw[MAX_LINE_LEN], previous_line[MAX_LINE_LEN]; + char *token, *value, *line_ptr; + void *insert_ptr = NULL; + boolean read_continued_line = FALSE; + File *file; + int line_nr = 0, token_count = 0, include_count = 0; + +#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING + token_value_separator_warning = FALSE; +#endif + +#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH + token_already_exists_warning = FALSE; +#endif + +#if 0 + Error(ERR_INFO, "===== opening file: '%s'", filename); +#endif + + if (!(file = openFile(filename, MODE_READ))) + { + Error(ERR_WARN, "cannot open configuration file '%s'", filename); + + return FALSE; + } + +#if 0 + Error(ERR_INFO, "===== reading file: '%s'", filename); +#endif + + /* use "insert pointer" to store list end for constant insertion complexity */ + if (!is_hash) + insert_ptr = setup_file_data; + + /* on top invocation, create hash to mark included files (to prevent loops) */ + if (top_recursion_level) + include_filename_hash = newSetupFileHash(); + + /* mark this file as already included (to prevent including it again) */ + setHashEntry(include_filename_hash, getBaseNamePtr(filename), "true"); + + while (!checkEndOfFile(file)) + { + /* read next line of input file */ + if (!getStringFromFile(file, line, MAX_LINE_LEN)) + break; + +#if 0 + Error(ERR_INFO, "got line: '%s'", line); +#endif + + /* check if line was completely read and is terminated by line break */ + if (strlen(line) > 0 && line[strlen(line) - 1] == '\n') + line_nr++; + + /* cut trailing line break (this can be newline and/or carriage return) */ + for (line_ptr = &line[strlen(line)]; line_ptr >= line; line_ptr--) + if ((*line_ptr == '\n' || *line_ptr == '\r') && *(line_ptr + 1) == '\0') + *line_ptr = '\0'; + + /* copy raw input line for later use (mainly debugging output) */ + strcpy(line_raw, line); + + if (read_continued_line) + { +#if 0 + /* !!! ??? WHY ??? !!! */ + /* cut leading whitespaces from input line */ + for (line_ptr = line; *line_ptr; line_ptr++) + if (*line_ptr != ' ' && *line_ptr != '\t') + break; +#endif + + /* append new line to existing line, if there is enough space */ + if (strlen(previous_line) + strlen(line_ptr) < MAX_LINE_LEN) + strcat(previous_line, line_ptr); + + strcpy(line, previous_line); /* copy storage buffer to line */ + + read_continued_line = FALSE; + } + + /* if the last character is '\', continue at next line */ + if (strlen(line) > 0 && line[strlen(line) - 1] == '\\') + { + line[strlen(line) - 1] = '\0'; /* cut off trailing backslash */ + strcpy(previous_line, line); /* copy line to storage buffer */ + + read_continued_line = TRUE; + + continue; + } + + if (!getTokenValueFromSetupLineExt(line, &token, &value, filename, + line_raw, line_nr, FALSE)) + continue; + + if (*token) + { + if (strEqual(token, "include")) + { + if (getHashEntry(include_filename_hash, value) == NULL) + { + char *basepath = getBasePath(filename); + char *basename = getBaseName(value); + char *filename_include = getPath2(basepath, basename); + +#if 0 + Error(ERR_INFO, "[including file '%s']", filename_include); +#endif + + loadSetupFileData(setup_file_data, filename_include, FALSE, is_hash); + + free(basepath); + free(basename); + free(filename_include); + + include_count++; + } + else + { + Error(ERR_WARN, "ignoring already processed file '%s'", value); + } + } + else + { + if (is_hash) + { +#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH + char *old_value = + getHashEntry((SetupFileHash *)setup_file_data, token); + + if (old_value != NULL) + { + if (!token_already_exists_warning) + { + Error(ERR_INFO_LINE, "-"); + Error(ERR_WARN, "duplicate token(s) found in config file:"); + Error(ERR_INFO, "- config file: '%s'", filename); + + token_already_exists_warning = TRUE; + } + + Error(ERR_INFO, "- token: '%s' (in line %d)", token, line_nr); + Error(ERR_INFO, " old value: '%s'", old_value); + Error(ERR_INFO, " new value: '%s'", value); + } +#endif + + setHashEntry((SetupFileHash *)setup_file_data, token, value); + } + else + { + insert_ptr = addListEntry((SetupFileList *)insert_ptr, token, value); + } + + token_count++; + } + } + } + + closeFile(file); + +#if CHECK_TOKEN_VALUE_SEPARATOR__WARN_IF_MISSING + if (token_value_separator_warning) + Error(ERR_INFO_LINE, "-"); +#endif + +#if CHECK_TOKEN__WARN_IF_ALREADY_EXISTS_IN_HASH + if (token_already_exists_warning) + Error(ERR_INFO_LINE, "-"); +#endif + + if (token_count == 0 && include_count == 0) + Error(ERR_WARN, "configuration file '%s' is empty", filename); + + if (top_recursion_level) + freeSetupFileHash(include_filename_hash); + + return TRUE; +} + +#else + static boolean loadSetupFileData(void *setup_file_data, char *filename, boolean top_recursion_level, boolean is_hash) { @@ -2044,6 +2282,8 @@ static boolean loadSetupFileData(void *setup_file_data, char *filename, return TRUE; } +#endif + #else static boolean loadSetupFileData(void *setup_file_data, char *filename, @@ -2375,10 +2615,11 @@ void checkSetupFileHashIdentifier(SetupFileHash *setup_file_hash, #define LEVELINFO_TOKEN_MUSIC_SET 18 #define LEVELINFO_TOKEN_FILENAME 19 #define LEVELINFO_TOKEN_FILETYPE 20 -#define LEVELINFO_TOKEN_HANDICAP 21 -#define LEVELINFO_TOKEN_SKIP_LEVELS 22 +#define LEVELINFO_TOKEN_SPECIAL_FLAGS 21 +#define LEVELINFO_TOKEN_HANDICAP 22 +#define LEVELINFO_TOKEN_SKIP_LEVELS 23 -#define NUM_LEVELINFO_TOKENS 23 +#define NUM_LEVELINFO_TOKENS 24 static LevelDirTree ldi; @@ -2406,6 +2647,7 @@ static struct TokenInfo levelinfo_tokens[] = { TYPE_STRING, &ldi.music_set, "music_set" }, { TYPE_STRING, &ldi.level_filename, "filename" }, { TYPE_STRING, &ldi.level_filetype, "filetype" }, + { TYPE_STRING, &ldi.special_flags, "special_flags" }, { TYPE_BOOLEAN, &ldi.handicap, "handicap" }, { TYPE_BOOLEAN, &ldi.skip_levels, "skip_levels" } }; @@ -2482,6 +2724,8 @@ static void setTreeInfoToDefaults(TreeInfo *ti, int type) ti->level_filename = NULL; ti->level_filetype = NULL; + ti->special_flags = NULL; + ti->levels = 0; ti->first_level = 0; ti->last_level = 0; @@ -2553,12 +2797,18 @@ static void setTreeInfoToDefaultsFromParent(TreeInfo *ti, TreeInfo *parent) ti->level_filename = NULL; ti->level_filetype = NULL; + ti->special_flags = getStringCopy(parent->special_flags); + ti->levels = 0; ti->first_level = 0; ti->last_level = 0; ti->level_group = FALSE; ti->handicap_level = 0; +#if 1 + ti->readonly = parent->readonly; +#else ti->readonly = TRUE; +#endif ti->handicap = TRUE; ti->skip_levels = FALSE; } @@ -2604,6 +2854,8 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) ti_copy->level_filename = getStringCopy(ti->level_filename); ti_copy->level_filetype = getStringCopy(ti->level_filetype); + ti_copy->special_flags = getStringCopy(ti->special_flags); + ti_copy->levels = ti->levels; ti_copy->first_level = ti->first_level; ti_copy->last_level = ti->last_level; @@ -2628,7 +2880,7 @@ static TreeInfo *getTreeInfoCopy(TreeInfo *ti) return ti_copy; } -static void freeTreeInfo(TreeInfo *ti) +void freeTreeInfo(TreeInfo *ti) { if (ti == NULL) return; @@ -2665,6 +2917,8 @@ static void freeTreeInfo(TreeInfo *ti) checked_free(ti->level_filename); checked_free(ti->level_filetype); + + checked_free(ti->special_flags); } checked_free(ti); @@ -2717,7 +2971,7 @@ static int compareTreeInfoEntries(const void *object1, const void *object2) { const TreeInfo *entry1 = *((TreeInfo **)object1); const TreeInfo *entry2 = *((TreeInfo **)object2); - int class_sorting1, class_sorting2; + int class_sorting1 = 0, class_sorting2 = 0; int compare_result; if (entry1->type == TREE_TYPE_LEVEL_DIR) @@ -2725,7 +2979,9 @@ static int compareTreeInfoEntries(const void *object1, const void *object2) class_sorting1 = LEVELSORTING(entry1); class_sorting2 = LEVELSORTING(entry2); } - else + else if (entry1->type == TREE_TYPE_GRAPHICS_DIR || + entry1->type == TREE_TYPE_SOUNDS_DIR || + entry1->type == TREE_TYPE_MUSIC_DIR) { class_sorting1 = ARTWORKSORTING(entry1); class_sorting2 = ARTWORKSORTING(entry2); @@ -2840,14 +3096,18 @@ static char *getCacheToken(char *prefix, char *suffix) return token; } -static char *getFileTimestamp(char *filename) +static char *getFileTimestampString(char *filename) { +#if 1 + return getStringCopy(i_to_a(getFileTimestampEpochSeconds(filename))); +#else struct stat file_status; if (stat(filename, &file_status) != 0) /* cannot stat file */ return getStringCopy(i_to_a(0)); return getStringCopy(i_to_a(file_status.st_mtime)); +#endif } static boolean modifiedFileTimestamp(char *filename, char *timestamp_string) @@ -2964,8 +3224,8 @@ static void setArtworkInfoCacheEntry(TreeInfo *artwork_info, LEVELINFO_FILENAME); char *filename_artworkinfo = getPath2(getSetupArtworkDir(artwork_info), ARTWORKINFO_FILENAME(type)); - char *timestamp_levelinfo = getFileTimestamp(filename_levelinfo); - char *timestamp_artworkinfo = getFileTimestamp(filename_artworkinfo); + char *timestamp_levelinfo = getFileTimestampString(filename_levelinfo); + char *timestamp_artworkinfo = getFileTimestampString(filename_artworkinfo); token_main = getCacheToken(token_prefix, "TIMESTAMP_LEVELINFO"); setHashEntry(artworkinfo_cache_new, token_main, timestamp_levelinfo); @@ -3004,8 +3264,8 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first, char *directory_name) { #if 0 - static unsigned long progress_delay = 0; - unsigned long progress_delay_value = 100; /* (in milliseconds) */ + static unsigned int progress_delay = 0; + unsigned int progress_delay_value = 100; /* (in milliseconds) */ #endif char *directory_path = getPath2(level_directory, directory_name); char *filename = getPath2(directory_path, LEVELINFO_FILENAME); @@ -3084,6 +3344,12 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first, leveldir_new->in_user_dir = (!strEqual(leveldir_new->basepath, options.level_directory)); +#if 0 + printf("::: '%s' -> %d\n", + leveldir_new->identifier, + leveldir_new->in_user_dir); +#endif + /* adjust some settings if user's private level directory was detected */ if (leveldir_new->sort_priority == LEVELCLASS_UNDEFINED && leveldir_new->in_user_dir && @@ -3155,6 +3421,101 @@ static boolean LoadLevelInfoFromLevelConf(TreeInfo **node_first, return TRUE; } +#if 1 +static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, + TreeInfo *node_parent, + char *level_directory) +{ + Directory *dir; + DirectoryEntry *dir_entry; + boolean valid_entry_found = FALSE; + +#if 0 + Error(ERR_INFO, "looking for levels in '%s' ...", level_directory); +#endif + + if ((dir = openDirectory(level_directory)) == NULL) + { + Error(ERR_WARN, "cannot read level directory '%s'", level_directory); + + return; + } + +#if 0 + Error(ERR_INFO, "opening '%s' succeeded ...", level_directory); +#endif + + while ((dir_entry = readDirectory(dir)) != NULL) /* loop all entries */ + { + char *directory_name = dir_entry->basename; + char *directory_path = getPath2(level_directory, directory_name); + +#if 0 + Error(ERR_INFO, "checking entry '%s' ...", directory_name); +#endif + + /* skip entries for current and parent directory */ + if (strEqual(directory_name, ".") || + strEqual(directory_name, "..")) + { + free(directory_path); + + continue; + } + +#if 1 + /* find out if directory entry is itself a directory */ + if (!dir_entry->is_directory) /* not a directory */ + { + free(directory_path); + +#if 0 + Error(ERR_INFO, "* entry '%s' is not a directory ...", directory_name); +#endif + + continue; + } +#else + /* find out if directory entry is itself a directory */ + struct stat file_status; + if (stat(directory_path, &file_status) != 0 || /* cannot stat file */ + (file_status.st_mode & S_IFMT) != S_IFDIR) /* not a directory */ + { + free(directory_path); + + continue; + } +#endif + + free(directory_path); + + if (strEqual(directory_name, GRAPHICS_DIRECTORY) || + strEqual(directory_name, SOUNDS_DIRECTORY) || + strEqual(directory_name, MUSIC_DIRECTORY)) + continue; + + valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent, + level_directory, + directory_name); + } + + closeDirectory(dir); + + /* special case: top level directory may directly contain "levelinfo.conf" */ + if (node_parent == NULL && !valid_entry_found) + { + /* check if this directory directly contains a file "levelinfo.conf" */ + valid_entry_found |= LoadLevelInfoFromLevelConf(node_first, node_parent, + level_directory, "."); + } + + if (!valid_entry_found) + Error(ERR_WARN, "cannot find any valid level series in directory '%s'", + level_directory); +} + +#else + static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, TreeInfo *node_parent, char *level_directory) @@ -3163,18 +3524,31 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, struct dirent *dir_entry; boolean valid_entry_found = FALSE; +#if 1 + Error(ERR_INFO, "looking for levels in '%s' ...", level_directory); +#endif + if ((dir = opendir(level_directory)) == NULL) { Error(ERR_WARN, "cannot read level directory '%s'", level_directory); + return; } +#if 1 + Error(ERR_INFO, "opening '%s' succeeded ...", level_directory); +#endif + while ((dir_entry = readdir(dir)) != NULL) /* loop until last dir entry */ { struct stat file_status; char *directory_name = dir_entry->d_name; char *directory_path = getPath2(level_directory, directory_name); +#if 1 + Error(ERR_INFO, "checking entry '%s' ...", directory_name); +#endif + /* skip entries for current and parent directory */ if (strEqual(directory_name, ".") || strEqual(directory_name, "..")) @@ -3217,6 +3591,7 @@ static void LoadLevelInfoFromLevelDir(TreeInfo **node_first, Error(ERR_WARN, "cannot find any valid level series in directory '%s'", level_directory); } +#endif boolean AdjustGraphicsForEMC() { @@ -3562,8 +3937,8 @@ void LoadArtworkInfoFromLevelInfo(ArtworkDirTree **artwork_node, LevelDirTree *level_node) { #if 0 - static unsigned long progress_delay = 0; - unsigned long progress_delay_value = 100; /* (in milliseconds) */ + static unsigned int progress_delay = 0; + unsigned int progress_delay_value = 100; /* (in milliseconds) */ #endif int type = (*artwork_node)->type; @@ -3879,12 +4254,16 @@ void LoadLevelSetup_LastSeries() free(filename); } -void SaveLevelSetup_LastSeries() +static void SaveLevelSetup_LastSeries_Ext(boolean deactivate_last_level_series) { /* ----------------------------------------------------------------------- */ /* ~/./levelsetup.conf */ /* ----------------------------------------------------------------------- */ + // check if the current level directory structure is available at this point + if (leveldir_current == NULL) + return; + char *filename = getPath2(getSetupDir(), LEVELSETUP_FILENAME); char *level_subdir = leveldir_current->subdir; FILE *file; @@ -3894,12 +4273,18 @@ void SaveLevelSetup_LastSeries() if (!(file = fopen(filename, MODE_WRITE))) { Error(ERR_WARN, "cannot write setup file '%s'", filename); + free(filename); + return; } fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_FILE_IDENTIFIER, getCookie("LEVELSETUP"))); + + if (deactivate_last_level_series) + fprintf(file, "# %s\n# ", "the following level set may have caused a problem and was deactivated"); + fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_LEVEL_SERIES, level_subdir)); @@ -3910,11 +4295,81 @@ void SaveLevelSetup_LastSeries() free(filename); } +void SaveLevelSetup_LastSeries() +{ + SaveLevelSetup_LastSeries_Ext(FALSE); +} + +void SaveLevelSetup_LastSeries_Deactivate() +{ + SaveLevelSetup_LastSeries_Ext(TRUE); +} + +#if 1 + +static void checkSeriesInfo() +{ + static char *level_directory = NULL; + Directory *dir; +#if 0 + DirectoryEntry *dir_entry; +#endif + + /* check for more levels besides the 'levels' field of 'levelinfo.conf' */ + + level_directory = getPath2((leveldir_current->in_user_dir ? + getUserLevelDir(NULL) : + options.level_directory), + leveldir_current->fullpath); + + if ((dir = openDirectory(level_directory)) == NULL) + { + Error(ERR_WARN, "cannot read level directory '%s'", level_directory); + + return; + } + +#if 0 + while ((dir_entry = readDirectory(dir)) != NULL) /* last directory entry */ + { + if (strlen(dir_entry->basename) > 4 && + dir_entry->basename[3] == '.' && + strEqual(&dir_entry->basename[4], LEVELFILE_EXTENSION)) + { + char levelnum_str[4]; + int levelnum_value; + + strncpy(levelnum_str, dir_entry->basename, 3); + levelnum_str[3] = '\0'; + + levelnum_value = atoi(levelnum_str); + + if (levelnum_value < leveldir_current->first_level) + { + Error(ERR_WARN, "additional level %d found", levelnum_value); + leveldir_current->first_level = levelnum_value; + } + else if (levelnum_value > leveldir_current->last_level) + { + Error(ERR_WARN, "additional level %d found", levelnum_value); + leveldir_current->last_level = levelnum_value; + } + } + } +#endif + + closeDirectory(dir); +} + +#else + static void checkSeriesInfo() { static char *level_directory = NULL; DIR *dir; +#if 0 struct dirent *dir_entry; +#endif /* check for more levels besides the 'levels' field of 'levelinfo.conf' */ @@ -3926,9 +4381,11 @@ static void checkSeriesInfo() if ((dir = opendir(level_directory)) == NULL) { Error(ERR_WARN, "cannot read level directory '%s'", level_directory); + return; } +#if 0 while ((dir_entry = readdir(dir)) != NULL) /* last directory entry */ { if (strlen(dir_entry->d_name) > 4 && @@ -3943,7 +4400,6 @@ static void checkSeriesInfo() levelnum_value = atoi(levelnum_str); -#if 0 if (levelnum_value < leveldir_current->first_level) { Error(ERR_WARN, "additional level %d found", levelnum_value); @@ -3954,22 +4410,31 @@ static void checkSeriesInfo() Error(ERR_WARN, "additional level %d found", levelnum_value); leveldir_current->last_level = levelnum_value; } -#endif } } +#endif closedir(dir); } +#endif + void LoadLevelSetup_SeriesInfo() { char *filename; SetupFileHash *level_setup_hash = NULL; char *level_subdir = leveldir_current->subdir; + int i; /* always start with reliable default values */ level_nr = leveldir_current->first_level; + for (i = 0; i < MAX_LEVELS; i++) + { + LevelStats_setPlayed(i, 0); + LevelStats_setSolved(i, 0); + } + checkSeriesInfo(leveldir_current); /* ----------------------------------------------------------------------- */ @@ -3984,6 +4449,8 @@ void LoadLevelSetup_SeriesInfo() { char *token_value; + /* get last played level in this level set */ + token_value = getHashEntry(level_setup_hash, TOKEN_STR_LAST_PLAYED_LEVEL); if (token_value) @@ -3996,6 +4463,8 @@ void LoadLevelSetup_SeriesInfo() level_nr = leveldir_current->last_level; } + /* get handicap level in this level set */ + token_value = getHashEntry(level_setup_hash, TOKEN_STR_HANDICAP_LEVEL); if (token_value) @@ -4013,6 +4482,31 @@ void LoadLevelSetup_SeriesInfo() leveldir_current->handicap_level = level_nr; } + /* get number of played and solved levels in this level set */ + + BEGIN_HASH_ITERATION(level_setup_hash, itr) + { + char *token = HASH_ITERATION_TOKEN(itr); + char *value = HASH_ITERATION_VALUE(itr); + + if (strlen(token) == 3 && + token[0] >= '0' && token[0] <= '9' && + token[1] >= '0' && token[1] <= '9' && + token[2] >= '0' && token[2] <= '9') + { + int level_nr = atoi(token); + + if (value != NULL) + LevelStats_setPlayed(level_nr, atoi(value)); /* read 1st column */ + + value = strchr(value, ' '); + + if (value != NULL) + LevelStats_setSolved(level_nr, atoi(value)); /* read 2nd column */ + } + } + END_HASH_ITERATION(hash, itr) + checkSetupFileHashIdentifier(level_setup_hash, filename, getCookie("LEVELSETUP")); @@ -4031,6 +4525,7 @@ void SaveLevelSetup_SeriesInfo() char *level_nr_str = int2str(level_nr, 0); char *handicap_level_str = int2str(leveldir_current->handicap_level, 0); FILE *file; + int i; /* ----------------------------------------------------------------------- */ /* ~/./levelsetup//levelsetup.conf */ @@ -4051,8 +4546,24 @@ void SaveLevelSetup_SeriesInfo() getCookie("LEVELSETUP"))); fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_LAST_PLAYED_LEVEL, level_nr_str)); - fprintf(file, "%s\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL, - handicap_level_str)); + fprintf(file, "%s\n\n", getFormattedSetupEntry(TOKEN_STR_HANDICAP_LEVEL, + handicap_level_str)); + + for (i = leveldir_current->first_level; i <= leveldir_current->last_level; + i++) + { + if (LevelStats_getPlayed(i) > 0 || + LevelStats_getSolved(i) > 0) + { + char token[16]; + char value[16]; + + sprintf(token, "%03d", i); + sprintf(value, "%d %d", LevelStats_getPlayed(i), LevelStats_getSolved(i)); + + fprintf(file, "%s\n", getFormattedSetupEntry(token, value)); + } + } fclose(file); @@ -4060,3 +4571,37 @@ void SaveLevelSetup_SeriesInfo() free(filename); } + +int LevelStats_getPlayed(int nr) +{ + return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].played : 0); +} + +int LevelStats_getSolved(int nr) +{ + return (nr >= 0 && nr < MAX_LEVELS ? level_stats[nr].solved : 0); +} + +void LevelStats_setPlayed(int nr, int value) +{ + if (nr >= 0 && nr < MAX_LEVELS) + level_stats[nr].played = value; +} + +void LevelStats_setSolved(int nr, int value) +{ + if (nr >= 0 && nr < MAX_LEVELS) + level_stats[nr].solved = value; +} + +void LevelStats_incPlayed(int nr) +{ + if (nr >= 0 && nr < MAX_LEVELS) + level_stats[nr].played++; +} + +void LevelStats_incSolved(int nr) +{ + if (nr >= 0 && nr < MAX_LEVELS) + level_stats[nr].solved++; +}