settings.c
author bjarni
Sat, 25 Feb 2006 21:25:23 +0000
changeset 3085 d56874235364
parent 3052 ebb8c7a94e5f
child 3112 a6fda4d4c7ca
permissions -rw-r--r--
(svn r3674) -Feature: [OSX] Added support for tripple binaries (binaries optimised for G3, G5 and i686)
G4 have no problems using G3 code while G5 can, but really benefit from getting their own optimised code (Apple: G5 is not just a fast G4)
Also changed FAT_BINARY to UNIVERSAL_BINARY since Apple removed most (all?) references to fat binaries on their homepage two days after I added FAT_BINARY
/* $Id$ */

#include "stdafx.h"
#include "openttd.h"
#include "currency.h"
#include "functions.h"
#include "macros.h"
#include "screenshot.h"
#include "sound.h"
#include "string.h"
#include "variables.h"
#include "network.h"
#include "settings.h"

typedef struct IniFile IniFile;
typedef struct IniItem IniItem;
typedef struct IniGroup IniGroup;
typedef struct SettingsMemoryPool SettingsMemoryPool;

static void pool_init(SettingsMemoryPool **pool);
static void *pool_alloc(SettingsMemoryPool **pool, uint size);
static void *pool_strdup(SettingsMemoryPool **pool, const char *mem, uint size);
static void pool_free(SettingsMemoryPool **pool);

struct SettingsMemoryPool {
	uint pos,size;
	SettingsMemoryPool *next;
	byte mem[1];
};

static SettingsMemoryPool *pool_new(uint minsize)
{
	SettingsMemoryPool *p;
	if (minsize < 4096 - 12) minsize = 4096 - 12;

	p = malloc(sizeof(SettingsMemoryPool) - 1 + minsize);
	p->pos = 0;
	p->size = minsize;
	p->next = NULL;
	return p;
}

static void pool_init(SettingsMemoryPool **pool)
{
	*pool = pool_new(0);
}

static void *pool_alloc(SettingsMemoryPool **pool, uint size)
{
	uint pos;
	SettingsMemoryPool *p = *pool;

	size = ALIGN(size, sizeof(void*));

	// first check if there's memory in the next pool
	if (p->next && p->next->pos + size <= p->next->size) {
		p = p->next;
	// then check if there's not memory in the cur pool
	} else if (p->pos + size > p->size) {
		SettingsMemoryPool *n = pool_new(size);
		*pool = n;
		n->next = p;
		p = n;
	}

	pos = p->pos;
	p->pos += size;
	return p->mem + pos;
}

static void *pool_strdup(SettingsMemoryPool **pool, const char *mem, uint size)
{
	byte *p = pool_alloc(pool, size + 1);
	p[size] = 0;
	memcpy(p, mem, size);
	return p;
}

static void pool_free(SettingsMemoryPool **pool)
{
	SettingsMemoryPool *p = *pool, *n;
	*pool = NULL;
	while (p) {
		n = p->next;
		free(p);
		p = n;
	}
}

// structs describing the ini format.
struct IniItem {
	char *name;
	char *value;
	char *comment;
	IniItem *next;
};

struct IniGroup {
	char *name; // name of group
	char *comment; //comment for group
	IniItem *item, **last_item;
	IniGroup *next;
	IniFile *ini;
	IniGroupType type; // type of group
};

struct IniFile {
	SettingsMemoryPool *pool;
	IniGroup *group, **last_group;
	char *comment; // last comment in file
};

// allocate an inifile object
static IniFile *ini_alloc(void)
{
	IniFile *ini;
	SettingsMemoryPool *pool;
	pool_init(&pool);
	ini = (IniFile*)pool_alloc(&pool, sizeof(IniFile));
	ini->pool = pool;
	ini->group = NULL;
	ini->last_group = &ini->group;
	ini->comment = NULL;
	return ini;
}

// allocate an ini group object
static IniGroup *ini_group_alloc(IniFile *ini, const char *grpt, int len)
{
	IniGroup *grp = pool_alloc(&ini->pool, sizeof(IniGroup));
	grp->ini = ini;
	grp->name = pool_strdup(&ini->pool, grpt, len);
	if (!strcmp(grp->name, "newgrf") || !strcmp(grp->name, "servers") || !strcmp(grp->name, "bans")) {
		grp->type = IGT_LIST;
	} else {
		grp->type = IGT_VARIABLES;
	}
	grp->next = NULL;
	grp->item = NULL;
	grp->comment = NULL;
	grp->last_item = &grp->item;
	*ini->last_group = grp;
	ini->last_group = &grp->next;
	return grp;
}

static IniItem *ini_item_alloc(IniGroup *group, const char *name, int len)
{
	IniItem *item = pool_alloc(&group->ini->pool, sizeof(IniItem));
	item->name = pool_strdup(&group->ini->pool, name, len);
	item->next = NULL;
	item->comment = NULL;
	item->value = NULL;
	*group->last_item = item;
	group->last_item = &item->next;
	return item;
}

// load an ini file into the "abstract" format
static IniFile *ini_load(const char *filename)
{
	char buffer[1024], c, *s, *t, *e;
	FILE *in;
	IniFile *ini;
	IniGroup *group = NULL;
	IniItem *item;

	char *comment = NULL;
	uint comment_size = 0;
	uint comment_alloc = 0;

	ini = ini_alloc();

	in = fopen(filename, "r");
	if (in == NULL) return ini;

	// for each line in the file
	while (fgets(buffer, sizeof(buffer), in)) {

		// trim whitespace from the left side
		for (s = buffer; s[0] == ' ' || s[0] == '\t'; s++);

		// trim whitespace from right side.
		e = s + strlen(s);
		while (e > s && ((c=e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
		*e = 0;

		// skip comments and empty lines
		if (*s == '#' || *s == 0) {
			uint ns = comment_size + (e - s + 1);
			uint a = comment_alloc;
			uint pos;
			// add to comment
			if (ns > a) {
				a = max(a, 128);
				do a*=2; while (a < ns);
				comment = realloc(comment, comment_alloc = a);
			}
			pos = comment_size;
			comment_size += (e - s + 1);
			comment[pos + e - s] = '\n'; // comment newline
			memcpy(comment + pos, s, e - s); // copy comment contents
			continue;
		}

		// it's a group?
		if (s[0] == '[') {
			if (e[-1] != ']')
				ShowInfoF("ini: invalid group name '%s'\n", buffer);
			else
				e--;
			s++; // skip [
			group = ini_group_alloc(ini, s, e - s);
			if (comment_size) {
				group->comment = pool_strdup(&ini->pool, comment, comment_size);
				comment_size = 0;
			}
		} else if (group) {
			// find end of keyname
			for (t=s; *t != 0 && *t != '=' && *t != '\t' && *t != ' '; t++) {}

			// it's an item in an existing group
			item = ini_item_alloc(group, s, t-s);
			if (comment_size) {
				item->comment = pool_strdup(&ini->pool, comment, comment_size);
				comment_size = 0;
			}

			// for list items, the name and value are the same:
			if (group->type == IGT_LIST) {
				item->value = item->name;
				continue;
			}

			// find start of parameter
			while (*t == '=' || *t == ' ' || *t == '\t') t++;


			// remove starting quotation marks
			if (*t == '\"') t++;
			// remove ending quotation marks
			e = t + strlen(t);
			if (e > t && e[-1] =='\"') e--;
			*e = 0;

			item->value = pool_strdup(&ini->pool, t, e - t);
		} else {
			// it's an orphan item
			ShowInfoF("ini: '%s' outside of group\n", buffer);
		}
	}

	if (comment_size) {
		ini->comment = pool_strdup(&ini->pool, comment, comment_size);
		comment_size = 0;
	}

	free(comment);
	fclose(in);

	return ini;
}

// lookup a group or make a new one
static IniGroup *ini_getgroup(IniFile *ini, const char *name, int len)
{
	IniGroup *group;

	if (len == -1) len = strlen(name);

	// does it exist already?
	for (group = ini->group; group; group = group->next)
		if (!memcmp(group->name, name, len) && group->name[len] == 0)
			return group;

	// otherwise make a new one
	group = ini_group_alloc(ini, name, len);
	group->comment = pool_strdup(&ini->pool, "\n", 1);
	return group;
}

// lookup an item or make a new one
static IniItem *ini_getitem(IniGroup *group, const char *name, bool create)
{
	IniItem *item;
	uint len = strlen(name);

	for (item = group->item; item; item = item->next)
		if (strcmp(item->name, name) == 0) return item;

	if (!create) return NULL;

	// otherwise make a new one
	return ini_item_alloc(group, name, len);
}

// save ini file from the "abstract" format.
static bool ini_save(const char *filename, IniFile *ini)
{
	FILE *f;
	IniGroup *group;
	IniItem *item;

	f = fopen(filename, "w");
	if (f == NULL) return false;

	for (group = ini->group; group != NULL; group = group->next) {
		if (group->comment) fputs(group->comment, f);
		fprintf(f, "[%s]\n", group->name);
		for (item = group->item; item != NULL; item = item->next) {
			if (item->comment) fputs(item->comment, f);
			if (group->type == IGT_LIST)
				fprintf(f, "%s\n", item->value ? item->value : "");
			else
				fprintf(f, "%s = %s\n", item->name, item->value ? item->value : "");
		}
	}
	if (ini->comment) fputs(ini->comment, f);

	fclose(f);
	return true;
}

static void ini_free(IniFile *ini)
{
	pool_free(&ini->pool);
}

/* Find the index value of a ONEofMANY type in a string seperated by |
 * @param many full domain of values the ONEofMANY setting can have
 * @param one the current value of the setting for which a value needs found
 * @param onelen force calculation of the *one parameter
 * @return the integer index of the full-list, or -1 if not found */
static int lookup_oneofmany(const char *many, const char *one, int onelen)
{
	const char *s;
	int idx;

	if (onelen == -1) onelen = strlen(one);

	// check if it's an integer
	if (*one >= '0' && *one <= '9')
		return strtoul(one, NULL, 0);

	idx = 0;
	for (;;) {
		// find end of item
		s = many;
		while (*s != '|' && *s != 0) s++;
		if (s - many == onelen && !memcmp(one, many, onelen)) return idx;
		if (*s == 0) return -1;
		many = s + 1;
		idx++;
	}
}

/* Find the set-integer value MANYofMANY type in a string
 * @param many full domain of values the MANYofMANY setting can have
 * @param str the current string value of the setting, each individual
 * of seperated by a whitespace\tab or | character
 * @return the 'fully' set integer, or -1 if a set is not found */
static uint32 lookup_manyofmany(const char *many, const char *str)
{
	const char *s;
	int r;
	uint32 res = 0;

	for (;;) {
		// skip "whitespace"
		while (*str == ' ' || *str == '\t' || *str == '|') str++;
		if (*str == 0) break;

		s = str;
		while (*s != 0 && *s != ' ' && *s != '\t' && *s != '|') s++;

		r = lookup_oneofmany(many, str, s - str);
		if (r == -1) return (uint32)-1;

		SETBIT(res, r); // value found, set it
		if (*s == 0) break;
		str = s + 1;
	}
	return res;
}

/** Parse an integerlist string and set each found value
 * @param p the string to be parsed. Each element in the list is seperated by a comma
 * @param items pointer to the integerlist-array that will be filled with values
 * @param maxitems the maximum number of elements the integerlist-array has
 * @return returns the number of items found, or -1 on an error */
static int parse_intlist(const char *p, int *items, int maxitems)
{
	int n = 0, v;
	char *end;

	for (;;) {
		v = strtol(p, &end, 0);
		if (p == end || n == maxitems) return -1;
		p = end;
		items[n++] = v;
		if (*p == 0) break;
		if (*p != ',') return -1;
		p++;
	}

	return n;
}

/* Load parsed string-values into an integer-array (intlist)
 * @param str the string that contains the values (and will be parsed)
 * @param array pointer to the integer-arrays that will be filled
 * @param nelems the number of elements the array holds. Maximum is 64 elements
 * @param type the type of elements the array holds (eg INT8, UINT16, etc.)
 * @return return true on success and false on error */
static bool load_intlist(const char *str, void *array, int nelems, int type)
{
	int items[64];
	int i, nitems;

	if (str == NULL) {
		memset(items, 0, sizeof(items));
		nitems = nelems;
	} else {
		nitems = parse_intlist(str, items, lengthof(items));
		if (nitems != nelems) return false;
	}

	switch (type) {
	case SDT_INT8:
	case SDT_UINT8:
		for (i = 0; i != nitems; i++) ((byte*)array)[i] = items[i];
		break;
	case SDT_INT16:
	case SDT_UINT16:
		for (i = 0; i != nitems; i++) ((uint16*)array)[i] = items[i];
		break;
	case SDT_INT32:
	case SDT_UINT32:
		for (i = 0; i != nitems; i++) ((uint32*)array)[i] = items[i];
		break;
	default: NOT_REACHED();
	}

	return true;
}

/* Convert an integer-array (intlist) to a string representation. Each value
 * is seperated by a comma
 * @param buf output buffer where the string-representation will be stored
 * @param array pointer to the integer-arrays that is read from
 * @param nelems the number of elements the array holds.
 * @param type the type of elements the array holds (eg INT8, UINT16, etc.) */
static void make_intlist(char *buf, const void *array, int nelems, int type)
{
	int i, v = 0;
	const byte *p = (const byte*)array;

	for (i = 0; i != nelems; i++) {
		switch (type) {
		case SDT_INT8:   v = *(int8*)p;   p += 1; break;
		case SDT_UINT8:  v = *(byte*)p;   p += 1; break;
		case SDT_INT16:  v = *(int16*)p;  p += 2; break;
		case SDT_UINT16: v = *(uint16*)p; p += 2; break;
		case SDT_INT32:  v = *(int32*)p;  p += 4; break;
		case SDT_UINT32: v = *(uint32*)p; p += 4; break;
		default: NOT_REACHED();
		}
		buf += sprintf(buf, (i == 0) ? "%d" : ",%d", v);
	}
}

/* Convert a ONEofMANY structure to a string representation.
 * @param buf output buffer where the string-representation will be stored
 * @param many the full-domain string of possible values
 * @param id the value of the variable and whose string-representation must be found */
static void make_oneofmany(char *buf, const char *many, int id)
{
	int orig_id = id;

	// Look for the id'th element
	while (--id >= 0) {
		for (; *many != '|'; many++) {
			if (*many == '\0') { // not found
				sprintf(buf, "%d", orig_id);
				return;
			}
		}
		many++; // pass the |-character
	}

	// copy string until next item (|) or the end of the list if this is the last one
	while (*many != '\0' && *many != '|') *buf++ = *many++;
	*buf = '\0';
}

/* Convert a MANYofMANY structure to a string representation.
 * @param buf output buffer where the string-representation will be stored
 * @param many the full-domain string of possible values
 * @param x the value of the variable and whose string-representation must
 *        be found in the bitmasked many string */
static void make_manyofmany(char *buf, const char *many, uint32 x)
{
	const char *start;
	int i = 0;
	bool init = true;

	for (; x != 0; x >>= 1, i++) {
		start = many;
		while (*many != 0 && *many != '|') many++; // advance to the next element

		if (HASBIT(x, 0)) { // item found, copy it
			if (!init) *buf++ = '|';
			init = false;
			if (start == many) {
				buf += sprintf(buf, "%d", i);
			} else {
				memcpy(buf, start, many - start);
				buf += many - start;
			}
		}

		if (*many == '|') many++;
	}

	*buf = '\0';
}

/* Get the GenericType of a setting. This describes the main type
 * @param desc pointer to SettingDesc structure
 * @return return GenericType, see SettingDescType */
static inline int GetSettingGenericType(const SettingDesc *desc)
{
	return desc->flags & 0xFF00; // GB(desc->flags, 8, 8) << 8;
}

/* Get the NumberType of a setting. This describes the integer type
 * @param desc pointer to SettingDesc structure
 * @return return NumberType, see SettingDescType */
static inline int GetSettingNumberType(const SettingDesc *desc)
{
	return desc->flags & 0xF0; // GB(desc->flags, 4, 8); << 4
}

/** Convert a string representation (external) of a setting to the internal rep.
 * @param desc SettingDesc struct that holds all information about the variable
 * @param str input string that will be parsed based on the type of desc
 * @return return the parsed value of the setting */
static const void *string_to_val(const SettingDesc *desc, const char *str)
{
	switch (GetSettingGenericType(desc)) {
	case SDT_NUMX: {
		char *end;
		unsigned long val = strtoul(str, &end, 0);
		if (*end != '\0') ShowInfoF("ini: trailing characters at end of setting '%s'", desc->name);
		return (void*)val;
	}
	case SDT_ONEOFMANY: {
		long r = lookup_oneofmany(desc->many, str, -1);
		if (r != -1) return (void*)r;
		ShowInfoF("ini: invalid value '%s' for '%s'", str, desc->name);
		return 0;
	}
	case SDT_MANYOFMANY: {
		unsigned long r = lookup_manyofmany(desc->many, str);
		if (r != (unsigned long)-1) return (void*)r;
		ShowInfoF("ini: invalid value '%s' for '%s'", str, desc->name);
		return 0;
	}
	case SDT_BOOLX:
		if (strcmp(str, "true")  == 0 || strcmp(str, "on")  == 0 || strcmp(str, "1") == 0)
			return (void*)true;
		if (strcmp(str, "false") == 0 || strcmp(str, "off") == 0 || strcmp(str, "0") == 0)
			return (void*)false;
		ShowInfoF("ini: invalid setting value '%s' for '%s'", str, desc->name);
		break;

	case SDT_STR:
	case SDT_STRB:
	case SDT_STRQ:
	case SDT_INTLIST:
	case SDT_CHAR:
		return str;
	}

	return NULL;
}

/** Load values from a group of an IniFile structure into the internal representation
 * @param ini pointer to IniFile structure that holds administrative information
 * @param desc pointer to SettingDesc structure whose internally pointed variables will
 *        be given values
 * @param grpname the group of the IniFile to search in for the new values */
static void load_setting_desc(IniFile *ini, const SettingDesc *desc, const char *grpname)
{
	IniGroup *group_def = ini_getgroup(ini, grpname, -1), *group;
	IniItem *item;
	const void *p;
	void *ptr;

	for (;desc->name != NULL; desc++) {
		// XXX - wtf is this?? (group override?)
		const char *s = strchr(desc->name, '.');
		if (s != NULL) {
			group = ini_getgroup(ini, desc->name, s - desc->name);
			s++;
		} else {
			s = desc->name;
			group = group_def;
		}

		item = ini_getitem(group, s, false);
		p = (item == NULL) ? desc->def : string_to_val(desc, item->value);

		/* get pointer to the variable */
		ptr = desc->ptr;

		/* The main type of a variable/setting is in bytes 8-15
		 * The subtype (what kind of numbers do we have there) is in 0-7 */
		switch (GetSettingGenericType(desc)) {
		case SDT_BOOLX: /* All four are various types of (integer) numbers */
		case SDT_NUMX:
		case SDT_ONEOFMANY:
		case SDT_MANYOFMANY:
			switch (GetSettingNumberType(desc)) {
			case SDT_INT8:
			case SDT_UINT8:  *(byte*)ptr   = (byte)(unsigned long)p;   break;
			case SDT_INT16:
			case SDT_UINT16: *(uint16*)ptr = (uint16)(unsigned long)p; break;
			case SDT_INT32:
			case SDT_UINT32: *(uint32*)ptr = (uint32)(unsigned long)p; break;
			default: NOT_REACHED(); break;
			}
			break;

		case SDT_STR:
			free(*(char**)ptr);
			*(char**)ptr = strdup((const char*)p);
			break;

		case SDT_STRB:
		case SDT_STRQ:
			if (p != NULL) ttd_strlcpy((char*)ptr, p, GB(desc->flags, 16, 16));
			break;

		case SDT_CHAR:
			*(char*)ptr = *(char*)p;
			break;

		case SDT_INTLIST: {
			if (!load_intlist(p, ptr, GB(desc->flags, 16, 16), GetSettingNumberType(desc)))
				ShowInfoF("ini: error in array '%s'", desc->name);
			break;
		}
		default: NOT_REACHED(); break;
		}
	}
}

/* Save the values of settings to the inifile.
 * @param ini pointer to IniFile structure
 * @param desc read-only SettingDesc structure which contains the unmodified,
 *        loaded values of the configuration file and various information about it
 * @param grpname holds the name of the group (eg. [network]) where these will be saved
 * The function works as follows: for each item in the SettingDesc structure we have
 * a look if the value has changed since we started the game (the original values
 * are reloaded when saving). If settings indeed have changed, we get these and save them.*/
static void save_setting_desc(IniFile *ini, const SettingDesc *desc, const char *grpname)
{
	IniGroup *group_def = NULL, *group;
	IniItem *item;
	const void *p;
	void *ptr;
	uint32 i = 0;
	char buf[512]; // setting buffer
	const char *s;

	for (; desc->name != NULL; desc++) {
		if (desc->flags & SDT_NOSAVE) continue;

		// XXX - wtf is this?? (group override?)
		s = strchr(desc->name, '.');
		if (s != NULL) {
			group = ini_getgroup(ini, desc->name, s - desc->name);
			s++;
		} else {
			if (group_def == NULL) group_def = ini_getgroup(ini, grpname, -1);
			s = desc->name;
			group = group_def;
		}

		item = ini_getitem(group, s, true);

		/* get pointer to the variable */
		ptr = desc->ptr;

		if (item->value != NULL) {
			// check if the value is the same as the old value
			p = string_to_val(desc, item->value);

			/* The main type of a variable/setting is in bytes 8-15
			* The subtype (what kind of numbers do we have there) is in 0-7 */
			switch (GetSettingGenericType(desc)) {
			case SDT_BOOLX:
			case SDT_NUMX:
			case SDT_ONEOFMANY:
			case SDT_MANYOFMANY:
				switch (GetSettingNumberType(desc)) {
				case SDT_INT8:
				case SDT_UINT8:
					if (*(byte*)ptr == (byte)(unsigned long)p) continue;
					break;
				case SDT_INT16:
				case SDT_UINT16:
					if (*(uint16*)ptr == (uint16)(unsigned long)p) continue;
					break;
				case SDT_INT32:
				case SDT_UINT32:
					if (*(uint32*)ptr == (uint32)(unsigned long)p) continue;
					break;
				default: NOT_REACHED();
				}
				break;
			case SDT_STR: assert(0); break;
			default: break; /* Assume the other types are always changed */
			}
		}

		/* Value has changed, get the new value and put it into a buffer */
		switch (GetSettingGenericType(desc)) {
		case SDT_BOOLX:
		case SDT_NUMX:
		case SDT_ONEOFMANY:
		case SDT_MANYOFMANY:
			switch (GetSettingNumberType(desc)) {
			case SDT_INT8:   i = *(int8*)ptr;   break;
			case SDT_UINT8:  i = *(byte*)ptr;   break;
			case SDT_INT16:  i = *(int16*)ptr;  break;
			case SDT_UINT16: i = *(uint16*)ptr; break;
			case SDT_INT32:  i = *(int32*)ptr;  break;
			case SDT_UINT32: i = *(uint32*)ptr; break;
			default: NOT_REACHED();
			}

			switch (GetSettingGenericType(desc)) {
			case SDT_BOOLX:
				strcpy(buf, (i != 0) ? "true" : "false");
				break;
			case SDT_NUMX:
				sprintf(buf, "%u", i);
				break;
			case SDT_ONEOFMANY:
				make_oneofmany(buf, desc->many, i);
				break;
			case SDT_MANYOFMANY:
				make_manyofmany(buf, desc->many, i);
				break;
			default: NOT_REACHED();
			}
			break;

		case SDT_STR:
			strcpy(buf, *(char**)ptr);
			break;
		case SDT_STRB:
			strcpy(buf, (char*)ptr);
			break;
		case SDT_STRQ:
			sprintf(buf, "\"%s\"", (char*)ptr);
			break;
		case SDT_CHAR:
			sprintf(buf, "\"%c\"", *(char*)ptr);
			break;
		case SDT_INTLIST:
			make_intlist(buf, ptr, GB(desc->flags, 16, 16), GetSettingNumberType(desc));
			break;
		}

		/* The value is different, that means we have to write it to the ini */
		item->value = pool_strdup(&ini->pool, buf, strlen(buf));
	}
}

//***************************
// OTTD specific INI stuff
//***************************

#ifndef EXTERNAL_PLAYER
#define EXTERNAL_PLAYER "timidity"
#endif

static const SettingDesc music_settings[] = {
	{"playlist",	SDT_UINT8,	(void*)0,			&msf.playlist, NULL},
	{"music_vol", SDT_UINT8,	(void*)128,		&msf.music_vol, NULL},
	{"effect_vol",SDT_UINT8,	(void*)128,		&msf.effect_vol, NULL},
	{"custom_1",	SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_1) << 16, NULL, &msf.custom_1, NULL},
	{"custom_2",	SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_2) << 16, NULL, &msf.custom_2, NULL},
	{"playing",		SDT_BOOL,		(void*)true,	&msf.playing, NULL},
	{"shuffle",		SDT_BOOL,		(void*)false, &msf.shuffle, NULL},
	{"extmidi",   SDT_STRINGBUF | (lengthof(msf.extmidi)<<16), EXTERNAL_PLAYER, &msf.extmidi, NULL},
	{NULL,				0,					NULL,					NULL,																NULL}
};

/* win32_v.c only settings */
#ifdef WIN32
extern bool _force_full_redraw, _double_size;
extern uint _display_hz, _fullscreen_bpp;

static const SettingDesc win32_settings[] = {
	{"display_hz",				SDT_UINT, (void*)0,			&_display_hz,					NULL},
	{"force_full_redraw", SDT_BOOL, (void*)false, &_force_full_redraw,	NULL},
	{"fullscreen_bpp",		SDT_UINT, (void*)8,			&_fullscreen_bpp,			NULL},
	{"double_size",				SDT_BOOL, (void*)false, &_double_size,				NULL},
	{NULL,								0,				NULL,					NULL,									NULL}
};
#endif /* WIN32 */

static const SettingDesc misc_settings[] = {
	{"display_opt",				SDT_MANYOFMANY | SDT_UINT8, (void*)(DO_SHOW_TOWN_NAMES|DO_SHOW_STATION_NAMES|DO_SHOW_SIGNS|DO_FULL_ANIMATION|DO_FULL_DETAIL|DO_TRANS_BUILDINGS|DO_WAYPOINTS), &_display_opt, "SHOW_TOWN_NAMES|SHOW_STATION_NAMES|SHOW_SIGNS|FULL_ANIMATION|TRANS_BUILDINGS|FULL_DETAIL|WAYPOINTS"},
	{"news_display_opt",	SDT_UINT32,		"0xAAAAAAAA",		&_news_display_opt,		NULL}, // default to all full messages: 10101010101010101010 = 0xAAAAAAAA
	{"news_ticker_sound", SDT_BOOL,     (void*)true, &_news_ticker_sound,   NULL},
	{"fullscreen",				SDT_BOOL,			(void*)false, &_fullscreen,					NULL},
	/* Added the (uint32) cast in the next 3 lines, to suppress a warning on 64bit targets -- TrueLight */
	{"videodriver",				SDT_STRINGBUF | ((uint32)lengthof(_ini_videodriver)<<16) | SDT_NOSAVE,NULL,			_ini_videodriver,				NULL},
	{"musicdriver",				SDT_STRINGBUF | ((uint32)lengthof(_ini_musicdriver)<<16) | SDT_NOSAVE,NULL,			_ini_musicdriver,				NULL},
	{"sounddriver",				SDT_STRINGBUF | ((uint32)lengthof(_ini_sounddriver)<<16) | SDT_NOSAVE,NULL,			_ini_sounddriver,				NULL},
	{"language",					SDT_STRINGBUF | lengthof(_dynlang.curr_file)<<16,							NULL,			_dynlang.curr_file,			NULL},
	{"resolution",				SDT_UINT16 | SDT_INTLIST | lengthof(_cur_resolution) << 16,		"640,480",_cur_resolution,				NULL},
	{"screenshot_format", SDT_STRINGBUF | (lengthof(_screenshot_format_name)<<16),			NULL,			_screenshot_format_name,NULL},
	{"savegame_format",		SDT_STRINGBUF | (lengthof(_savegame_format)<<16),							NULL,			_savegame_format,				NULL},
	{"rightclick_emulate",SDT_BOOL,			(void*)false, &_rightclick_emulate, NULL},
	{NULL,								0,						NULL,					NULL,									NULL}
};

#ifdef ENABLE_NETWORK
static const SettingDesc network_settings[] = {
	{"sync_freq",				SDT_UINT16 | SDT_NOSAVE,	(void*)100,			&_network_sync_freq,		NULL},
	{"frame_freq",			SDT_UINT8 | SDT_NOSAVE,	(void*)0,			&_network_frame_freq,		NULL},
	{"max_join_time",		SDT_UINT16,	(void*)500,	&_network_max_join_time,	NULL},
	{"pause_on_join",		SDT_BOOL, (void*)false, &_network_pause_on_join, NULL},
	{"server_bind_ip",	SDT_STRINGBUF | (lengthof(_network_server_bind_ip_host) << 16),	"0.0.0.0",	&_network_server_bind_ip_host,	NULL},
	{"server_port",			SDT_UINT,	(void*)NETWORK_DEFAULT_PORT,	&_network_server_port,	NULL},
	{"server_advertise",SDT_BOOL, (void*)false, &_network_advertise, NULL},
	{"lan_internet",		SDT_UINT8, (void*)0, &_network_lan_internet, NULL},
	{"player_name",			SDT_STRINGBUF | (lengthof(_network_player_name) << 16), NULL, &_network_player_name, NULL},
	{"server_password",	SDT_STRINGBUF | (lengthof(_network_server_password) << 16), NULL, &_network_server_password, NULL},
	{"rcon_password",		SDT_STRINGBUF | (lengthof(_network_rcon_password) << 16), NULL, &_network_rcon_password, NULL},
	{"server_name",			SDT_STRINGBUF | (lengthof(_network_server_name) << 16), NULL, &_network_server_name, NULL},
	{"connect_to_ip",		SDT_STRINGBUF | (lengthof(_network_default_ip) << 16), NULL, &_network_default_ip, NULL},
	{"network_id",			SDT_STRINGBUF | (lengthof(_network_unique_id) << 16), NULL, &_network_unique_id, NULL},
	{"autoclean_companies", SDT_BOOL, (void*)false, &_network_autoclean_companies, NULL},
	{"autoclean_unprotected", SDT_UINT8, (void*)12, &_network_autoclean_unprotected, NULL},
	{"autoclean_protected", SDT_UINT8, (void*)36, &_network_autoclean_protected, NULL},
	{"restart_game_date", SDT_UINT16, (void*)0, &_network_restart_game_date, NULL},
	{NULL,							0,											NULL,					NULL,										NULL}
};
#endif /* ENABLE_NETWORK */

/* The settings showed when opened in the intro-menu. These values also are saved to
 * openttd.cfg, thus _opt_newgame is used here (not _opt which is used ingame with loaded games!) */
static const SettingDesc gameopt_settings[] = {
  {"diff_level",  SDT_UINT8,                  (void*)9, &_opt_newgame.diff_level, NULL},
  {"diff_custom", SDT_INTLIST | SDT_UINT32 | (sizeof(GameDifficulty)/4) << 16, NULL, &_opt_newgame.diff, NULL},
  {"currency",    SDT_UINT8 | SDT_ONEOFMANY,  (void*)0, &_opt_newgame.currency,   "GBP|USD|EUR|YEN|ATS|BEF|CHF|CZK|DEM|DKK|ESP|FIM|FRF|GRD|HUF|ISK|ITL|NLG|NOK|PLN|ROL|RUR|SEK|custom" },
  {"distances",   SDT_UINT8 | SDT_ONEOFMANY,  (void*)1, &_opt_newgame.kilometers, "imperial|metric" },
  {"town_names",  SDT_UINT8 | SDT_ONEOFMANY,  (void*)0, &_opt_newgame.town_name,  "english|french|german|american|latin|silly|swedish|dutch|finnish|polish|slovakish|norwegian|hungarian|austrian|romanian|czech|swiss" },
  {"landscape",   SDT_UINT8 | SDT_ONEOFMANY,  (void*)0, &_opt_newgame.landscape,  "normal|hilly|desert|candy" },
  {"autosave",    SDT_UINT8 | SDT_ONEOFMANY,  (void*)1, &_opt_newgame.autosave,   "off|monthly|quarterly|half year|yearly" },
  {"road_side",   SDT_UINT8 | SDT_ONEOFMANY,  (void*)1, &_opt_newgame.road_side,  "left|right" },
  {NULL,          0,                          NULL,     NULL,                     NULL}
};

// The player-based settings (are not send over the network)
// Not everything can just be added to this list. For example, service_interval
//  can not be done, because every client assigns the service_interval value to the
//  v->service_interval, meaning that every client assigns his value to the interval.
//  If the setting was player-based, that would mean that vehicles could deside on
//  different moments that they are heading back to a service depot, causing desyncs
//  on a massive scale.
// Short, you can only add settings that does stuff for the screen, GUI, that kind
//  of stuff.
static const SettingDesc patch_player_settings[] = {
	{"vehicle_speed",				SDT_BOOL,		(void*)true,	&_patches.vehicle_speed,				NULL},

	{"lost_train_days",			SDT_UINT16, (void*)180,		&_patches.lost_train_days,			NULL},
	{"train_income_warn",		SDT_BOOL,		(void*)true,	&_patches.train_income_warn,		NULL},
	{"order_review_system", SDT_UINT8,	(void*)2,			&_patches.order_review_system,	NULL},

	{"status_long_date",		SDT_BOOL,		(void*)true,	&_patches.status_long_date,			NULL},
	{"show_finances",				SDT_BOOL,		(void*)true,	&_patches.show_finances,				NULL},
	{"autoscroll",					SDT_BOOL,		(void*)false,	&_patches.autoscroll,						NULL},
	{"reverse_scroll",      SDT_BOOL,   (void*)false, &_patches.reverse_scroll,       NULL},
	{"errmsg_duration",			SDT_UINT8,	(void*)5,			&_patches.errmsg_duration,			NULL},
	{"toolbar_pos",					SDT_UINT8,	(void*)0,			&_patches.toolbar_pos,					NULL},
	{"keep_all_autosave",		SDT_BOOL,		(void*)false, &_patches.keep_all_autosave,		NULL},
	{"autosave_on_exit",		SDT_BOOL,		(void*)false, &_patches.autosave_on_exit,			NULL},
	{"max_autosave_num",			SDT_UINT8, (void*)16,		&_patches.max_num_autosaves,			NULL},

	{"bridge_pillars",			SDT_BOOL,		(void*)true,	&_patches.bridge_pillars,				NULL},
	{"invisible_trees",			SDT_BOOL,		(void*)false, &_patches.invisible_trees,			NULL},
	{"drag_signals_density",SDT_UINT8,	(void*)4,			&_patches.drag_signals_density, NULL},

	{"window_snap_radius",  SDT_UINT8,  (void*)10,    &_patches.window_snap_radius,   NULL},

	{"autorenew",						SDT_BOOL,		(void*)false,	&_patches.autorenew,						NULL},
	{"autorenew_months",		SDT_INT16,	(void*)-6,		&_patches.autorenew_months,			NULL},
	{"autorenew_money",			SDT_INT32,	(void*)100000,&_patches.autorenew_money,			NULL},

	{"population_in_label",	SDT_BOOL,		(void*)true,	&_patches.population_in_label,	NULL},
	{"link_terraform_toolbar",       SDT_BOOL,   (void*)false, &_patches.link_terraform_toolbar,       NULL},

	{NULL,									0,					NULL,					NULL,																						NULL}
};

// Non-static, needed in network_server.c
const SettingDesc patch_settings[] = {
	{"build_on_slopes",			SDT_BOOL,		(void*)true,	&_patches.build_on_slopes,			NULL},
	{"mammoth_trains",			SDT_BOOL,		(void*)true,	&_patches.mammoth_trains,				NULL},
	{"join_stations",				SDT_BOOL,		(void*)true,	&_patches.join_stations,				NULL},
	{"station_spread",			SDT_UINT8,	(void*)12,		&_patches.station_spread,				NULL},
	{"full_load_any",				SDT_BOOL,		(void*)true,	&_patches.full_load_any,				NULL},
	{"modified_catchment", 	SDT_BOOL,		(void*)true,	&_patches.modified_catchment,		NULL},


	{"inflation",						SDT_BOOL,		(void*)true,	&_patches.inflation,						NULL},
	{"selectgoods",					SDT_BOOL,		(void*)true,	&_patches.selectgoods,					NULL},
	{"longbridges",					SDT_BOOL,		(void*)true, &_patches.longbridges,					NULL},
	{"gotodepot",						SDT_BOOL,		(void*)true,	&_patches.gotodepot,						NULL},

	{"build_rawmaterial_ind",	SDT_BOOL, (void*)false, &_patches.build_rawmaterial_ind,NULL},
	{"multiple_industry_per_town",SDT_BOOL, (void*)false, &_patches.multiple_industry_per_town, NULL},
	{"same_industry_close",	SDT_BOOL,		(void*)false, &_patches.same_industry_close,	NULL},

	{"signal_side",					SDT_BOOL,		(void*)true,	&_patches.signal_side,					NULL},

	{"new_nonstop",					SDT_BOOL,		(void*)false,	&_patches.new_nonstop,					NULL},
	{"roadveh_queue",				SDT_BOOL,		(void*)true,	&_patches.roadveh_queue,				NULL},

	{"snow_line_height",		SDT_UINT8,	(void*)7,			&_patches.snow_line_height,			NULL},

	{"bribe",								SDT_BOOL,		(void*)true,	&_patches.bribe,								NULL},

	{"nonuniform_stations",	SDT_BOOL,		(void*)true,	&_patches.nonuniform_stations,	NULL},
	{"always_small_airport",SDT_BOOL,		(void*)false,	&_patches.always_small_airport,	NULL},
	{"realistic_acceleration",SDT_BOOL, (void*)false,	&_patches.realistic_acceleration,	NULL},
	{"wagon_speed_limits",  SDT_BOOL,   (void*)true,  &_patches.wagon_speed_limits,   NULL},
	{"forbid_90_deg",				SDT_BOOL, 	(void*)false, &_patches.forbid_90_deg,					NULL},
	{"improved_load",				SDT_BOOL,		(void*)false,	&_patches.improved_load,				NULL},

	{"max_trains",					SDT_UINT16,	(void*)500,		&_patches.max_trains,						NULL},
	{"max_roadveh",					SDT_UINT16,	(void*)500,		&_patches.max_roadveh,					NULL},
	{"max_aircraft",				SDT_UINT16,	(void*)200,		&_patches.max_aircraft,					NULL},
	{"max_ships",						SDT_UINT16,	(void*)300,		&_patches.max_ships,						NULL},

	{"servint_ispercent",		SDT_BOOL,		(void*)false,	&_patches.servint_ispercent,		NULL},
	{"servint_trains",			SDT_UINT16, (void*)150,		&_patches.servint_trains,				NULL},
	{"servint_roadveh",			SDT_UINT16, (void*)150,		&_patches.servint_roadveh,			NULL},
	{"servint_ships",				SDT_UINT16, (void*)360,		&_patches.servint_ships,				NULL},
	{"servint_aircraft",		SDT_UINT16, (void*)100,		&_patches.servint_aircraft,			NULL},
	{"no_servicing_if_no_breakdowns", SDT_BOOL, (void*)0, &_patches.no_servicing_if_no_breakdowns, NULL},

	{"pf_maxlength",				SDT_UINT16, (void*)512,		&_patches.pf_maxlength,					NULL},
	{"pf_maxdepth",					SDT_UINT8,	(void*)16,		&_patches.pf_maxdepth,					NULL},


	{"ai_disable_veh_train",SDT_BOOL,		(void*)false, &_patches.ai_disable_veh_train,	NULL},
	{"ai_disable_veh_roadveh",SDT_BOOL,	(void*)false, &_patches.ai_disable_veh_roadveh,	NULL},
	{"ai_disable_veh_aircraft",SDT_BOOL,(void*)false, &_patches.ai_disable_veh_aircraft,NULL},
	{"ai_disable_veh_ship",	SDT_BOOL,		(void*)false, &_patches.ai_disable_veh_ship,	NULL},
	{"starting_date",				SDT_UINT32, (void*)1950,	&_patches.starting_date,				NULL},
	{"ending_date",				  SDT_UINT32, (void*)2051,	&_patches.ending_date,				  NULL},

	{"colored_news_date",		SDT_UINT32, (void*)2000,	&_patches.colored_news_date,		NULL},

	{"extra_dynamite",			SDT_BOOL,		(void*)false, &_patches.extra_dynamite,				NULL},

	{"never_expire_vehicles",SDT_BOOL,	(void*)false, &_patches.never_expire_vehicles,NULL},
	{"extend_vehicle_life",	SDT_UINT8,	(void*)0,			&_patches.extend_vehicle_life,	NULL},

	{"auto_euro",						SDT_BOOL,		(void*)true,	&_patches.auto_euro,						NULL},

	{"serviceathelipad",		SDT_BOOL,		(void*)true,	&_patches.serviceathelipad,			NULL},
	{"smooth_economy",			SDT_BOOL,		(void*)true,	&_patches.smooth_economy,				NULL},
	{"allow_shares",				SDT_BOOL,		(void*)true,	&_patches.allow_shares,					NULL},
	{"dist_local_authority",SDT_UINT8,	(void*)20,		&_patches.dist_local_authority, NULL},

	{"wait_oneway_signal",	SDT_UINT8,	(void*)15,		&_patches.wait_oneway_signal,		NULL},
	{"wait_twoway_signal",	SDT_UINT8,	(void*)41,		&_patches.wait_twoway_signal,		NULL},

	{"ainew_active",				SDT_BOOL,		(void*)false, &_patches.ainew_active,					NULL},
	{"ai_in_multiplayer",		SDT_BOOL,		(void*)false, &_patches.ai_in_multiplayer,		NULL},

	{"map_x", SDT_UINT32, (void*)8, &_patches.map_x, NULL},
	{"map_y", SDT_UINT32, (void*)8, &_patches.map_y, NULL},

	/* New Path Finding */
	{"new_pathfinding_all",	SDT_BOOL,		(void*)false, &_patches.new_pathfinding_all,	NULL},

	/* The maximum number of nodes to search */
	{"npf_max_search_nodes",   SDT_UINT32, (void*)10000,  &_patches.npf_max_search_nodes,		NULL},

	/* When a red signal is encountered, a small detour can be made around
	* it. This specifically occurs when a track is doubled, in which case
	* the detour is typically 2 tiles. It is also often used at station
	* entrances, when there is a choice of multiple platforms. If we take
	* a typical 4 platform station, the detour is 4 tiles. To properly
	* support larger stations we increase this value.
	* We want to prevent that trains that want to leave at one side of a
	* station, leave through the other side, turn around, enter the
	* station on another platform and exit the station on the right side
	* again, just because the sign at the right side was red. If we take
	* a typical 5 length station, this detour is 10 or 11 tiles (not
	* sure), so we set the default penalty at 10 (the station tile
	* penalty will further prevent this.
	* We give presignal exits (and combo's) a different (larger) penalty, because we really
	* don't want trains waiting in front of a presignal exit. */
	{"npf_rail_firstred_penalty",   SDT_UINT32, (void*)(10 * NPF_TILE_LENGTH),  &_patches.npf_rail_firstred_penalty,    NULL},
	{"npf_rail_firstred_exit_penalty", SDT_UINT32, (void*)(100 * NPF_TILE_LENGTH), &_patches.npf_rail_firstred_exit_penalty, NULL},
	/* This penalty is for when the last signal before the target is red.
	 * This is useful for train stations, where there are multiple
	 * platforms to choose from, which lie in different signal blocks.
	 * Every target in a occupied signal block (ie an occupied platform)
	 * will get this penalty.
	 */
	{"npf_rail_lastred_penalty",    SDT_UINT32, (void*)(10 * NPF_TILE_LENGTH),  &_patches.npf_rail_lastred_penalty,		NULL},
	/* When a train plans a route over a station tile, this penalty is
	* applied. We want that trains plan a route around a typical, 4x5
	* station, which means two tiles to the right, and two tiles back to
	* the left around it, or 5 tiles of station through it. If we assign
	* a penalty of 1 tile for every station tile passed, the route will
	* be around it.
	*/
	{"npf_rail_station_penalty",    SDT_UINT32, (void*)(1 * NPF_TILE_LENGTH),   &_patches.npf_rail_station_penalty,     NULL},
	{"npf_rail_slope_penalty",      SDT_UINT32, (void*)(1 * NPF_TILE_LENGTH),   &_patches.npf_rail_slope_penalty,       NULL},
	/* This penalty is applied when a train makes a turn. Its value of 1 makes
	 * sure that it has a minimal impact on the pathfinding, only when two
	 * paths have equal length it will make a difference */
	{"npf_rail_curve_penalty",      SDT_UINT32, (void*)(1),                     &_patches.npf_rail_curve_penalty,       NULL},
	/* Ths penalty is applied when a vehicle reverses inside a depot (doesn't
	 * apply to ships, as they can just come out the other end). XXX: Is this a
	 * good value? */
	{"npf_rail_depot_reverse_penalty", SDT_UINT32, (void*)(NPF_TILE_LENGTH * 50), &_patches.npf_rail_depot_reverse_penalty, NULL},
	{"npf_buoy_penalty",            SDT_UINT32, (void*)(2 * NPF_TILE_LENGTH),   &_patches.npf_buoy_penalty,             NULL},
	/* This penalty is applied when a ship makes a turn. It is bigger than the
	 * rail curve penalty, since ships (realisticly) have more trouble with
	 * making turns */
	{"npf_water_curve_penalty",     SDT_UINT32, (void*)(NPF_TILE_LENGTH / 4),   &_patches.npf_water_curve_penalty,      NULL},
	/* This is the penalty for road, same as for rail. */
	{"npf_road_curve_penalty",      SDT_UINT32, (void*)(1),                     &_patches.npf_road_curve_penalty,       NULL},
	/* This is the penalty for level crossings, for both road and rail vehicles */
 	{"npf_crossing_penalty",        SDT_UINT32, (void*)(3 * NPF_TILE_LENGTH),   &_patches.npf_crossing_penalty,         NULL},

	{NULL,                          0,          NULL,                           NULL,                                   NULL}
};

static const SettingDesc currency_settings[] = {
	{ "rate",      SDT_UINT16,                                               (void*)1,   &_custom_currency.rate,      NULL },
	{ "separator", SDT_CHAR,                                                 ".",        &_custom_currency.separator, NULL },
	{ "to_euro",   SDT_UINT16,                                               (void*)0,   &_custom_currency.to_euro,   NULL },
	{ "prefix",    SDT_STRINGQUOT | lengthof(_custom_currency.prefix) << 16, NULL,       &_custom_currency.prefix,    NULL },
	{ "suffix",    SDT_STRINGQUOT | lengthof(_custom_currency.suffix) << 16, " credits", &_custom_currency.suffix,    NULL },
	{ NULL, 0, NULL, NULL, NULL }
};

typedef void SettingDescProc(IniFile *ini, const SettingDesc *desc, const char *grpname);

static void HandleSettingDescs(IniFile *ini, SettingDescProc *proc)
{
	proc(ini, misc_settings,		"misc");
#ifdef WIN32
	proc(ini, win32_settings,		"win32");
#endif /* WIN32 */
#ifdef ENABLE_NETWORK
	proc(ini, network_settings, "network");
#endif /* ENABLE_NETWORK */
	proc(ini, music_settings,		"music");
	proc(ini, gameopt_settings, "gameopt");
	proc(ini, patch_settings,		"patches");
	proc(ini, patch_player_settings,		"patches");
	proc(ini, currency_settings,"currency");
}

// loads all items from a *grpname section into the **list
static void LoadList(IniFile *ini, const char *grpname, char **list, int len)
{
	IniGroup *group = ini_getgroup(ini, grpname, -1);
	IniItem *item;
	int i;

	if (!group)
		return;
	item = group->item;
	for ( i=0; i != len; i++) {
		if (item == NULL) break;
		list[i] = strdup(item->value);
		item = item->next;
	}
}

static void SaveList(IniFile *ini, const char *grpname, char **list, int len)
{
	IniGroup *group = ini_getgroup(ini, grpname, -1);
	IniItem *item = NULL;
	int i;
	bool first = true;

	if (group == NULL) return;
	group->item = NULL;

	for (i = 0; i != len; i++) {
		if (list[i] == NULL || list[i][0] == '\0') continue;

		if (first) { // add first item to the head of the group
			item = ini_item_alloc(group, list[i], strlen(list[i]));
			item->value = item->name;
			group->item = item;
			first = false;
		} else { // all other items are attached to the previous one
			item->next = ini_item_alloc(group, list[i], strlen(list[i]));
			item = item->next;
			item->value = item->name;
		}
	}
}

void LoadFromConfig(void)
{
	IniFile *ini = ini_load(_config_file);
	HandleSettingDescs(ini, load_setting_desc);
	LoadList(ini, "newgrf", _newgrf_files, lengthof(_newgrf_files));
#ifdef ENABLE_NETWORK
	LoadList(ini, "servers", _network_host_list, lengthof(_network_host_list));
	LoadList(ini, "bans", _network_ban_list, lengthof(_network_ban_list));
#endif /* ENABLE_NETWORK */
	ini_free(ini);
}

void SaveToConfig(void)
{
	IniFile *ini = ini_load(_config_file);
	HandleSettingDescs(ini, save_setting_desc);
#ifdef ENABLE_NETWORK
	SaveList(ini, "servers", _network_host_list, lengthof(_network_host_list));
	SaveList(ini, "bans", _network_ban_list, lengthof(_network_ban_list));
#endif /* ENABLE_NETWORK */
	ini_save(_config_file, ini);
	ini_free(ini);
}

void CheckConfig(void)
{
	// fix up news_display_opt from old to new
	int i;
	uint32 tmp;
	for (i = 0, tmp = _news_display_opt; i != 10; i++, tmp >>= 2) {
		if ((tmp & 0x3) == 0x3) { // old settings
			_news_display_opt = 0xAAAAAAAA; // set all news-messages to full 1010101010...
			break;
		}
	}

	// Increase old default values for pf_maxdepth and pf_maxlength
	// to support big networks.
	if (_patches.pf_maxdepth == 16 && _patches.pf_maxlength == 512) {
		_patches.pf_maxdepth = 48;
		_patches.pf_maxlength = 4096;
	}
}