settings.c
author Darkvater
Thu, 02 Mar 2006 00:07:41 +0000
changeset 3116 031230112b25
parent 3115 1f4949f5dc5d
child 3117 8066b8d0802b
permissions -rw-r--r--
(svn r3720) - [2/4] Present the game with a unified structure for the configuration-ini, saveload, console and gui representations of the settings. This second part gets rid of the old SettingDesc structures and updates them with the unified types. These new settings are heavily macro'd, which should make it easy to add/edit entries within sections.
- This unified structure merges the settings in settings.c (concerned with the ini file) and the ones in setting_gui., having to do with the GUI. I tried to give sensible min and maximum values to _patches that were not present in the GUI I hope they work out all right.
- All patch settings miss their callback function which have been temporarily reset to NULL
- Fixed a really small typo in a Doxygen comment in settings.h
- NOTE! When for example _patches are saved to the savegame (as are _gameopt_settings now), you cannot just blindly edit the struct. The same rules apply as to all the other SaveLoad structs. Eg correct CONDVAR's and only adding new entries at the end.
- NOTE! The game is not compilable after this commit.
/* $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"
#include "saveload.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, VarType 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 SLE_VAR_BL:
	case SLE_VAR_I8:
	case SLE_VAR_U8:
		for (i = 0; i != nitems; i++) ((byte*)array)[i] = items[i];
		break;
	case SLE_VAR_I16:
	case SLE_VAR_U16:
		for (i = 0; i != nitems; i++) ((uint16*)array)[i] = items[i];
		break;
	case SLE_VAR_I32:
	case SLE_VAR_U32:
		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, VarType type)
{
	int i, v = 0;
	const byte *p = (const byte*)array;

	for (i = 0; i != nelems; i++) {
		switch (type) {
		case SLE_VAR_BL:
		case SLE_VAR_I8:  v = *(int8*)p;   p += 1; break;
		case SLE_VAR_U8:  v = *(byte*)p;   p += 1; break;
		case SLE_VAR_I16: v = *(int16*)p;  p += 2; break;
		case SLE_VAR_U16: v = *(uint16*)p; p += 2; break;
		case SLE_VAR_I32: v = *(int32*)p;  p += 4; break;
		case SLE_VAR_U32: 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';
}

/** 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 SettingDescBase *desc, const char *str)
{
	switch (desc->cmd) {
	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_STRING:
	case SDT_INTLIST: return str;
	}

	return NULL;
}

/** Set the value of a setting and if needed clamp the value to
 * the preset minimum and maximum.
 * @param ptr the variable itself
 * @param sd pointer to the 'information'-database of the variable
 * @param val signed long version of the new value
 * @pre SettingDesc is of type SDT_BOOLX, SDT_NUMX,
 * SDT_ONEOFMANY or SDT_MANYOFMANY. Other types are not supported as of now */
static void Write_ValidateSetting(void *ptr, const SettingDesc *sd, int32 val)
{
	const SettingDescBase *sdb = &sd->desc;
	int32 min;

	if (sdb->cmd != SDT_BOOLX && sdb->cmd != SDT_NUMX &&
		  sdb->cmd != SDT_ONEOFMANY && sdb->cmd != SDT_MANYOFMANY) return;

	/* Override the minimum value. No value below sdb->min, except special value 0 */
	min = ((sdb->flags & SGF_0ISDISABLED) && val <= sdb->min) ? 0 : sdb->min;
	/* We cannot know the maximum value of a bitset variable, so just have faith */
	val = (sdb->cmd == SDT_MANYOFMANY) ? val : clamp(val, min, sdb->max);

	WriteValue(ptr, sd->save.conv, (int64)val);
}

/** 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 ini_load_settings(IniFile *ini, const SettingDesc *sd, const char *grpname, void *object)
{
	IniGroup *group;
	IniGroup *group_def = ini_getgroup(ini, grpname, -1);
	IniItem *item;
	const void *p;
	void *ptr;
	const char *s;

	for (; sd->save.cmd != SL_END; sd++) {
		const SettingDescBase *sdb = &sd->desc;
		const SaveLoad        *sld = &sd->save;

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

		item = ini_getitem(group, s, false);
		p = (item == NULL) ? sdb->def : string_to_val(sdb, item->value);
		ptr = ini_get_variable(sld, object);

		switch (sdb->cmd) {
		case SDT_BOOLX: /* All four are various types of (integer) numbers */
		case SDT_NUMX:
		case SDT_ONEOFMANY:
		case SDT_MANYOFMANY:
			Write_ValidateSetting(ptr, sd, (unsigned long)p); break;

		case SDT_STRING:
			switch (GetVarMemType(sld->conv)) {
				case SLE_VAR_STRB:
				case SLE_VAR_STRQ:
					if (p != NULL) ttd_strlcpy((char*)ptr, p, sld->length);
					break;
				case SLE_VAR_CHAR: *(char*)ptr = *(char*)p; break;
				default: NOT_REACHED(); break;
			}
			break;

		case SDT_INTLIST: {
			if (!load_intlist(p, ptr, sld->length, GetVarMemType(sld->conv)))
				ShowInfoF("ini: error in array '%s'", sdb->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 ini_save_settings(IniFile *ini, const SettingDesc *sd, const char *grpname, void *object)
{
	IniGroup *group_def = NULL, *group;
	IniItem *item;
	char buf[512];
	const char *s;
	void *ptr;

	for (; sd->save.cmd != SL_END; sd++) {
		const SettingDescBase *sdb = &sd->desc;
		const SaveLoad        *sld = &sd->save;

		/* If the setting is not saved to the configuration
		 * file, just continue with the next setting */
		if (sld->conv & SLF_CONFIG_NO) continue;

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

		item = ini_getitem(group, s, true);
		ptr = ini_get_variable(sld, object);

		if (item->value != NULL) {
			// check if the value is the same as the old value
			const void *p = string_to_val(sdb, 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 (sdb->cmd) {
			case SDT_BOOLX:
			case SDT_NUMX:
			case SDT_ONEOFMANY:
			case SDT_MANYOFMANY:
				switch (GetVarMemType(sld->conv)) {
				case SLE_VAR_BL:
				case SLE_VAR_I8:
				case SLE_VAR_U8:
					if (*(byte*)ptr == (byte)(unsigned long)p) continue;
					break;
				case SLE_VAR_I16:
				case SLE_VAR_U16:
					if (*(uint16*)ptr == (uint16)(unsigned long)p) continue;
					break;
				case SLE_VAR_I32:
				case SLE_VAR_U32:
					if (*(uint32*)ptr == (uint32)(unsigned long)p) continue;
					break;
				default: NOT_REACHED();
				}
				break;
			default: break; /* Assume the other types are always changed */
			}
		}

		/* Value has changed, get the new value and put it into a buffer */
		switch (sdb->cmd) {
		case SDT_BOOLX:
		case SDT_NUMX:
		case SDT_ONEOFMANY:
		case SDT_MANYOFMANY: {
			uint32 i = (uint32)ReadValue(ptr, sld->conv);

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

		case SDT_STRING:
			switch (GetVarMemType(sld->conv)) {
			case SLE_VAR_STRB: strcpy(buf, (char*)ptr); break;
			case SLE_VAR_STRQ: sprintf(buf, "\"%s\"", (char*)ptr); break;
			case SLE_VAR_CHAR: sprintf(buf, "\"%c\"", *(char*)ptr); break;
			default: NOT_REACHED();
			}
			break;

		case SDT_INTLIST:
			make_intlist(buf, ptr, sld->length, GetVarMemType(sld->conv));
			break;
		default: NOT_REACHED();
		}

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

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

	if (group == NULL) return;

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

static void ini_save_setting_list(IniFile *ini, const char *grpname, char **list, uint len)
{
	IniGroup *group = ini_getgroup(ini, grpname, -1);
	IniItem *item = NULL;
	uint 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;
		}
	}
}

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

/** Settings-macro usage:
 * The list might look daunting at first, but is in general easy to understand.
 * We have two types of list:
 * 1. SDTG_something
 * 2. SDT_something
 * The 'G' stands for global, so this is the one you will use for a SettingDescGlobVarList
 * section meaning global variables. The other uses a Base/Offset and runtime variable
 * selection mechanism, known from the saveload convention (it also has global so it
 * should not be hard).
 * Of each type there are again two versions, the normal one and one prefixed with 'COND'.
 * COND means that the setting is only valid in certain savegame versions (since patches
 * are saved to the savegame, this bookkeeping is necessary.
 * Now there are a lot of types. Easy ones are:
 * - VAR:  any number type, 'type' field specifies what number. eg int8 or uint32
 * - BOOL: a boolean number type
 * - STR:  a string or character. 'type' field specifies what string. Normal, string, or quoted
 * A bit more difficult to use are MMANY (meaning ManyOfMany) and OMANY (OneOfMany)
 * These are actually normal numbers, only bitmasked. In MMANY several bits can be
 * set, in the other only one.
 * The most complex type is INTLIST. This is basically an array of numbers. If
 * the intlist is only valid in certain savegame versions because for example
 * it has grown in size its length cannot be automatically be calculated so
 * use SDT(G)_CONDLISTO() meaning Old.
 * If nothing fits you, you can use the GENERAL macros, but it exposes the internal
 * structure somewhat so it needs a little looking. There are _NULL() macros as
 * well, these fill up space so you can add more patches there (in place) and you
 * DON'T have to increase the savegame version. */

#define NSD_GENERAL(name, def, cmd, guiflags, min, max, many, str, proc)\
	{name, (const void*)def, cmd, guiflags, min, max, many, str, proc}

/* Macros for various objects to go in the configuration file.
 * This section is for global variables */
#define SDTG_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, var, length, def, min, max, full, str, proc, from, to)\
{NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, full, str, proc), SLEG_GENERAL(sle_cmd, var, type | flags, length, from, to)}

#define SDTG_CONDVAR(name, type, flags, guiflags, var, def, min, max, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_NUMX, SL_VAR, type, flags, guiflags, var, 0, def, min, max, NULL, str, proc, from, to)
#define SDTG_VAR(name, type, flags, guiflags, var, def, min, max, str, proc)\
	SDTG_CONDVAR(name, type, flags, guiflags, var, def, min, max, str, proc, 0, SL_MAX_VERSION)

#define SDTG_CONDBOOL(name, flags, guiflags, var, def, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_BOOLX, SL_VAR, SLE_UINT8, flags, guiflags, var, 0, def, 0, 1, NULL, str, proc, from, to)
#define SDTG_BOOL(name, flags, guiflags, var, def, str, proc)\
	SDTG_CONDBOOL(name, flags, guiflags, var, def, str, proc, 0, SL_MAX_VERSION)

#define SDTG_CONDLIST(name, type, length, flags, guiflags, var, def, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, length, def, 0, 0, NULL, str, proc, from, to)
#define SDTG_LIST(name, type, flags, guiflags, var, def, str, proc)\
	SDTG_GENERAL(name, SDT_INTLIST, SL_ARR, type, flags, guiflags, var, lengthof(var), def, 0, 0, NULL, str, proc, 0, SL_MAX_VERSION)

#define SDTG_CONDSTR(name, type, length, flags, guiflags, var, def, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, length, def, 0, 0, NULL, str, proc, from, to)
#define SDTG_STR(name, type, flags, guiflags, var, def, str, proc)\
	SDTG_GENERAL(name, SDT_STRING, SL_STR, type, flags, guiflags, var, lengthof(var), def, 0, 0, NULL, str, proc, 0, SL_MAX_VERSION)

#define SDTG_CONDOMANY(name, type, flags, guiflags, var, def, max, full, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, max, full, str, proc, from, to)
#define SDTG_OMANY(name, type, flags, guiflags, var, def, max, full, str, proc)\
	SDTG_CONDOMANY(name, type, flags, guiflags, var, def, max full, str, proc, 0, SL_MAX_VERSION)

#define SDTG_CONDMMANY(name, type, flags, guiflags, var, def, full, str, proc, from, to)\
	SDTG_GENERAL(name, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, var, 0, def, 0, 0, full, str, proc, from, to)
#define SDTG_MMANY(name, type, flags, guiflags, var, def, full, str, proc)\
	SDTG_CONDMMANY(name, type, flags, guiflags, var, def, full, str, proc, 0, SL_MAX_VERSION)

#define SDTG_END() {{NULL, NULL, 0, 0, 0, 0, NULL, STR_NULL, NULL}, SLEG_END()}

/* Macros for various objects to go in the configuration file.
 * This section is for structures where their various members are saved */
#define SDT_GENERAL(name, sdt_cmd, sle_cmd, type, flags, guiflags, base, var, length, def, min, max, full, str, proc, from, to)\
	{NSD_GENERAL(name, def, sdt_cmd, guiflags, min, max, full, str, proc), SLE_GENERAL(sle_cmd, base, var, type | flags, length, from, to)}

#define SDT_CONDVAR(base, var, type, from, to, flags, guiflags, def, min, max, str, proc)\
	SDT_GENERAL(#var, SDT_NUMX, SL_VAR, type, flags, guiflags, base, var, 1, def, min, max, NULL, str, proc, from, to)
#define SDT_VAR(base, var, type, flags, guiflags, def, min, max, str, proc)\
	SDT_CONDVAR(base, var, type, 0, SL_MAX_VERSION, flags, guiflags, def, min, max, str, proc)

#define SDT_CONDBOOL(base, var, from, to, flags, guiflags, def, str, proc)\
	SDT_GENERAL(#var, SDT_BOOLX, SL_VAR, SLE_BOOL, flags, guiflags, base, var, 1, def, 0, 1, NULL, str, proc, from, to)
#define SDT_BOOL(base, var, flags, guiflags, def, str, proc)\
	SDT_CONDBOOL(base, var, 0, SL_MAX_VERSION, flags, guiflags, def, str, proc)

#define SDT_CONDLIST(base, var, type, from, to, flags, guiflags, def, str, proc)\
	SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, NULL, str, proc, from, to)
#define SDT_LIST(base, var, type, flags, guiflags, def, str, proc)\
	SDT_CONDLIST(base, var, type, 0, SL_MAX_VERSION, flags, guiflags, def, str, proc)
#define SDT_CONDLISTO(base, var, length, type, from, to, flags, guiflags, def, str, proc)\
	SDT_GENERAL(#var, SDT_INTLIST, SL_ARR, type, flags, guiflags, base, var, length, def, 0, 0, NULL, str, proc, from, to)

#define SDT_CONDSTR(base, var, type, from, to, flags, guiflags, def, str, proc)\
	SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, guiflags, base, var, lengthof(((base*)8)->var), def, 0, 0, NULL, str, proc, from, to)
#define SDT_STR(base, var, type, flags, guiflags, def, str, proc)\
	SDT_CONDSTR(base, var, type, 0, SL_MAX_VERSION, flags, guiflags, def, str, proc)
#define SDT_CONDSTRO(base, var, length, type, from, to, flags, def, str, proc)\
	SDT_GENERAL(#var, SDT_STRING, SL_STR, type, flags, 0, base, var, length, def, 0, 0, NULL, str, proc, from, to)

#define SDT_CONDCHR(base, var, from, to, flags, guiflags, def, str, proc)\
	SDT_GENERAL(#var, SDT_STRING, SL_VAR, SLE_CHAR, flags, guiflags, base, var, 1, def, 0, 0, NULL, str, proc, from, to)
#define SDT_CHR(base, var, flags, guiflags, def, str, proc)\
	SDT_CONDCHR(base, var, 0, SL_MAX_VERSION, flags, guiflags, def, str, proc)

#define SDT_CONDOMANY(base, var, type, from, to, flags, guiflags, def, max, full, str, proc)\
	SDT_GENERAL(#var, SDT_ONEOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, max, full, str, proc, from, to)
#define SDT_OMANY(base, var, type, flags, guiflags, def, max, full, str, proc)\
	SDT_CONDOMANY(base, var, type, 0, SL_MAX_VERSION, flags, guiflags, def, max, full, str, proc)

#define SDT_CONDMMANY(base, var, type, from, to, flags, guiflags, def, full, str, proc)\
	SDT_GENERAL(#var, SDT_MANYOFMANY, SL_VAR, type, flags, guiflags, base, var, 1, def, 0, 0, full, str, proc, from, to)
#define SDT_MMANY(base, var, type, flags, guiflags, def, full, str, proc)\
	SDT_CONDMMANY(base, var, type, 0, SL_MAX_VERSION, flags, guiflags, def, full, str, proc)

#define SDT_END() {{NULL, NULL, 0, 0, 0, 0, NULL, STR_NULL, NULL}, SLE_END()}

/* Shortcuts for macros below. Logically if we don't save the value
 * we also don't sync it in a network game */
#define S SLF_SAVE_NO | SLF_NETWORK_NO
#define C SLF_CONFIG_NO
#define N SLF_NETWORK_NO

#define D0 SGF_0ISDISABLED
#define NC SGF_NOCOMMA
#define MS SGF_MULTISTRING
#define NO SGF_NETWORK_ONLY
#define CR SGF_CURRENCY

#include "table/strings.h"
#ifndef EXTERNAL_PLAYER
#define EXTERNAL_PLAYER "timidity"
#endif

static const SettingDesc _music_settings[] = {
	 SDT_VAR(MusicFileSettings, playlist,  SLE_UINT8, S, 0,   0, 0,   5, STR_NULL, NULL),
	 SDT_VAR(MusicFileSettings, music_vol, SLE_UINT8, S, 0, 128, 0, 100, STR_NULL, NULL),
	 SDT_VAR(MusicFileSettings, effect_vol,SLE_UINT8, S, 0, 128, 0, 100, STR_NULL, NULL),
	SDT_LIST(MusicFileSettings, custom_1,  SLE_UINT8, S, 0, NULL,        STR_NULL, NULL),
	SDT_LIST(MusicFileSettings, custom_2,  SLE_UINT8, S, 0, NULL,        STR_NULL, NULL),
	SDT_BOOL(MusicFileSettings, playing,              S, 0, true,        STR_NULL, NULL),
	SDT_BOOL(MusicFileSettings, shuffle,              S, 0,false,        STR_NULL, NULL),
	 SDT_STR(MusicFileSettings, extmidi,    SLE_STRB, S, 0, EXTERNAL_PLAYER,STR_NULL, NULL),
	 SDT_END()
};

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

extern const SettingDescGlobVarList _win32_settings[] = {
	 SDTG_VAR("display_hz",     SLE_UINT, S, 0, _display_hz,       0, 0, 120, STR_NULL, NULL),
	SDTG_BOOL("force_full_redraw",        S, 0, _force_full_redraw,false,     STR_NULL, NULL),
	 SDTG_VAR("fullscreen_bpp", SLE_UINT, S, 0, _fullscreen_bpp,   8, 8,  32, STR_NULL, NULL),
	SDTG_BOOL("double_size",              S, 0, _double_size,      false,     STR_NULL, NULL),
	 SDTG_END()
};
#endif /* WIN32 */

static const SettingDescGlobVarList _misc_settings[] = {
	SDTG_MMANY("display_opt",     SLE_UINT8, S, 0, _display_opt,       (DO_SHOW_TOWN_NAMES|DO_SHOW_STATION_NAMES|DO_SHOW_SIGNS|DO_FULL_ANIMATION|DO_FULL_DETAIL|DO_TRANS_BUILDINGS|DO_WAYPOINTS), "SHOW_TOWN_NAMES|SHOW_STATION_NAMES|SHOW_SIGNS|FULL_ANIMATION|TRANS_BUILDINGS|FULL_DETAIL|WAYPOINTS", STR_NULL, NULL),
	  SDTG_VAR("news_display_opt", SLE_UINT, S, 0, _news_display_opt,0xAAAAAAAA,0,0xAAAAAAAA,STR_NULL, NULL), // default to all full messages: 10101010101010101010 = 0xAAAAAAAA
	 SDTG_BOOL("news_ticker_sound",          S, 0, _news_ticker_sound,     true,    STR_NULL, NULL),
	 SDTG_BOOL("fullscreen",                 S, 0, _fullscreen,           false,    STR_NULL, NULL),
	  SDTG_STR("videodriver",      SLE_STRB,C|S,0, _ini_videodriver,       NULL,    STR_NULL, NULL),
	  SDTG_STR("musicdriver",      SLE_STRB,C|S,0, _ini_musicdriver,       NULL,    STR_NULL, NULL),
	  SDTG_STR("sounddriver",      SLE_STRB,C|S,0, _ini_sounddriver,       NULL,    STR_NULL, NULL),
	  SDTG_STR("language",         SLE_STRB, S, 0, _dynlang.curr_file,     NULL,    STR_NULL, NULL),
	 SDTG_LIST("resolution",     SLE_UINT16, S, 0, _cur_resolution,   "640,480",    STR_NULL, NULL),
	  SDTG_STR("screenshot_format",SLE_STRB, S, 0, _screenshot_format_name,NULL,    STR_NULL, NULL),
	  SDTG_STR("savegame_format",  SLE_STRB, S, 0, _savegame_format,       NULL,    STR_NULL, NULL),
	 SDTG_BOOL("rightclick_emulate",         S, 0, _rightclick_emulate,   false,    STR_NULL, NULL),
	  SDTG_END()
};

#ifdef ENABLE_NETWORK
static const SettingDescGlobVarList _network_settings[] = {
	 SDTG_VAR("sync_freq",           SLE_UINT16,C|S,0, _network_sync_freq,            100, 0,  100,STR_NULL, NULL),
	 SDTG_VAR("frame_freq",           SLE_UINT8,C|S,0, _network_frame_freq,             0, 0,  100,STR_NULL, NULL),
	 SDTG_VAR("max_join_time",       SLE_UINT16, S, 0, _network_max_join_time,        500, 0,32000,STR_NULL, NULL),
	SDTG_BOOL("pause_on_join",                   S, 0, _network_pause_on_join,        false,       STR_NULL, NULL),
	 SDTG_STR("server_bind_ip",        SLE_STRB, S, 0, _network_server_bind_ip_host,  "0.0.0.0",   STR_NULL, NULL),
	 SDTG_VAR("server_port",         SLE_UINT16, S, 0, _network_server_port,          NETWORK_DEFAULT_PORT, 0, -1,STR_NULL, NULL),
	SDTG_BOOL("server_advertise",                S, 0, _network_advertise,            false,       STR_NULL, NULL),
	SDTG_BOOL("lan_internet",                    S, 0, _network_lan_internet,         false,       STR_NULL, NULL),
	 SDTG_STR("player_name",           SLE_STRB, S, 0, _network_player_name,          NULL,        STR_NULL, NULL),
	 SDTG_STR("server_password",       SLE_STRB, S, 0, _network_server_password,      NULL,        STR_NULL, NULL),
	 SDTG_STR("rcon_password",         SLE_STRB, S, 0, _network_rcon_password,        NULL,        STR_NULL, NULL),
	 SDTG_STR("server_name",           SLE_STRB, S, 0, _network_server_name,          NULL,        STR_NULL, NULL),
	 SDTG_STR("connect_to_ip",         SLE_STRB, S, 0, _network_default_ip,           NULL,        STR_NULL, NULL),
	 SDTG_STR("network_id",            SLE_STRB, S, 0, _network_unique_id,            NULL,        STR_NULL, NULL),
	SDTG_BOOL("autoclean_companies",             S, 0, _network_autoclean_companies,  false,       STR_NULL, NULL),
	 SDTG_VAR("autoclean_unprotected",SLE_UINT8, S, 0, _network_autoclean_unprotected,12, 0,  60,  STR_NULL, NULL),
	 SDTG_VAR("autoclean_protected",  SLE_UINT8, S, 0, _network_autoclean_protected,  36, 0, 180,  STR_NULL, NULL),
	 SDTG_VAR("restart_game_date",   SLE_UINT16, S, 0, _network_restart_game_date,    MAX_YEAR_BEGIN_REAL, MAX_YEAR_BEGIN_REAL, MAX_YEAR_END_REAL, STR_NULL, NULL),
	 SDTG_END()
};
#endif /* ENABLE_NETWORK */

static const SettingDesc _gameopt_settings[] = {
	/* In version 4 a new difficulty setting has been added to the difficulty settings,
	 * town attitude towards demolishing. Needs special handling because some dimwit thought
	 * it funny to have the GameDifficulty struct be an array while it is a struct of
	 * same-sized members
	 * XXX - To save file-space and since values are never bigger than about 10? only
	 * save the first 16 bits in the savegame. Question is why the values are still int32
	 * and why not byte for example? */
	SDT_GENERAL("diff_custom", SDT_INTLIST, SL_ARR, (SLE_FILE_I16 | SLE_VAR_I32), 0, 0, GameOptions, diff, 17, 0, 0, 0, NULL, STR_NULL, NULL, 0, 3),
	SDT_GENERAL("diff_custom", SDT_INTLIST, SL_ARR, (SLE_FILE_I16 | SLE_VAR_I32), 0, 0, GameOptions, diff, 18, 0, 0, 0, NULL, STR_NULL, NULL, 4, SL_MAX_VERSION),
	    SDT_VAR(GameOptions, diff_level,SLE_UINT8, 0, 0, 9,0, 9, STR_NULL, NULL),
	  SDT_OMANY(GameOptions, currency,  SLE_UINT8, N, 0, 0,  23, "GBP|USD|EUR|YEN|ATS|BEF|CHF|CZK|DEM|DKK|ESP|FIM|FRF|GRD|HUF|ISK|ITL|NLG|NOK|PLN|ROL|RUR|SEK|custom", STR_NULL, NULL),
	  SDT_OMANY(GameOptions, kilometers,SLE_UINT8, N, 0, 1,   1, "imperial|metric", STR_NULL, NULL),
	  SDT_OMANY(GameOptions, town_name, SLE_UINT8, 0, 0, 0,  16, "english|french|german|american|latin|silly|swedish|dutch|finnish|polish|slovakish|norwegian|hungarian|austrian|romanian|czech|swiss", STR_NULL, NULL),
	  SDT_OMANY(GameOptions, landscape, SLE_UINT8, 0, 0, 0,   3, "normal|hilly|desert|candy", STR_NULL, NULL),
	    SDT_VAR(GameOptions, snow_line, SLE_UINT8, 0, 0, 1,0,56, STR_NULL, NULL),
	  SDT_OMANY(GameOptions, autosave,  SLE_UINT8, N, 0, 1,   4, "off|monthly|quarterly|half year|yearly", STR_NULL, NULL),
	  SDT_OMANY(GameOptions, road_side, SLE_UINT8, 0, 0, 1,   1, "left|right", STR_NULL, NULL),
	    SDT_END()
};

/* Some patches do not need to be synchronised when playing in multiplayer.
 * These include for example the GUI settings and will not be saved with the
 * savegame.
 * It is also a bit tricky since you would think that service_interval
 * for example doesn't need to be synched. Every client assigns the service_interval
 * value to the v->service_interval, meaning that every client assigns his value. If
 * the setting was player-based, that would mean that vehicles could decide on
 * different moments that they are heading back to a service depot, causing desyncs
 * on a massive scale. */
const SettingDesc _patch_settings[] = {
	/***************************************************************************/
	/* User-interface section of the GUI-configure patches window (00 - 12) */
	SDT_BOOL(Patches, vehicle_speed,                 S, 0,  true,    STR_CONFIG_PATCHES_VEHICLESPEED,          NULL),
	SDT_BOOL(Patches, status_long_date,              S, 0,  true,    STR_CONFIG_PATCHES_LONGDATE,              NULL),
	SDT_BOOL(Patches, show_finances,                 S, 0,  true,    STR_CONFIG_PATCHES_SHOWFINANCES,          NULL),
	SDT_BOOL(Patches, autoscroll,                    S, 0, false,    STR_CONFIG_PATCHES_AUTOSCROLL,            NULL),
	SDT_BOOL(Patches, reverse_scroll,                S, 0, false,    STR_CONFIG_PATCHES_REVERSE_SCROLLING,     NULL),
	 SDT_VAR(Patches, errmsg_duration,    SLE_UINT8, S, 0,  5, 0,20, STR_CONFIG_PATCHES_ERRMSG_DURATION,       NULL),
	 SDT_VAR(Patches, toolbar_pos,        SLE_UINT8, S,MS,  0, 0, 2, STR_CONFIG_PATCHES_TOOLBAR_POS,           NULL),//&v_PositionMainToolbar),
	 SDT_VAR(Patches, window_snap_radius, SLE_UINT8, S,D0, 10, 1,32, STR_CONFIG_PATCHES_SNAP_RADIUS,           NULL),
	SDT_BOOL(Patches, invisible_trees,               S, 0, false,    STR_CONFIG_PATCHES_INVISIBLE_TREES,       NULL),//&InvisibleTreesActive),
	SDT_BOOL(Patches, population_in_label,           S, 0,  true,    STR_CONFIG_PATCHES_POPULATION_IN_LABEL,   NULL),//&PopulationInLabelActive),
	 SDT_VAR(Patches, map_x,              SLE_UINT8, S, 0,  8, 6,11, STR_CONFIG_PATCHES_MAP_X,                 NULL),
	 SDT_VAR(Patches, map_y,              SLE_UINT8, S, 0,  8, 6,11, STR_CONFIG_PATCHES_MAP_Y,                 NULL),
	SDT_BOOL(Patches, link_terraform_toolbar,        S, 0, false,    STR_CONFIG_PATCHES_LINK_TERRAFORM_TOOLBAR,NULL),

	/***************************************************************************/
	/* Construction section of the GUI-configure patches window (13 - 18) */
	SDT_BOOL(Patches, build_on_slopes,               0, 0,  true,    STR_CONFIG_PATCHES_BUILDONSLOPES,       NULL),
	SDT_BOOL(Patches, extra_dynamite,                0, 0, false,    STR_CONFIG_PATCHES_EXTRADYNAMITE,       NULL),
	SDT_BOOL(Patches, longbridges,                   0, 0,  true,    STR_CONFIG_PATCHES_LONGBRIDGES,         NULL),
	SDT_BOOL(Patches, signal_side,                   N, 0,  true,    STR_CONFIG_PATCHES_SIGNALSIDE,          NULL),
	SDT_BOOL(Patches, always_small_airport,          0, 0, false,    STR_CONFIG_PATCHES_SMALL_AIRPORTS,      NULL),
	 SDT_VAR(Patches, drag_signals_density,SLE_UINT8,S, 0,  4, 1,20, STR_CONFIG_PATCHES_DRAG_SIGNALS_DENSITY,NULL),

	/***************************************************************************/
	/* Vehicle section of the GUI-configure patches window (19 - 42) */
	SDT_BOOL(Patches, realistic_acceleration,        0, 0, false,                STR_CONFIG_PATCHES_REALISTICACCEL,       NULL),
	SDT_BOOL(Patches, forbid_90_deg,                 0, 0, false,                STR_CONFIG_PATCHES_FORBID_90_DEG,        NULL),
	SDT_BOOL(Patches, mammoth_trains,                0, 0,  true,                STR_CONFIG_PATCHES_MAMMOTHTRAINS,        NULL),
	SDT_BOOL(Patches, gotodepot,                     0, 0,  true,                STR_CONFIG_PATCHES_GOTODEPOT,            NULL),
	SDT_BOOL(Patches, roadveh_queue,                 0, 0,  true,                STR_CONFIG_PATCHES_ROADVEH_QUEUE,        NULL),
	SDT_BOOL(Patches, new_pathfinding_all,           0, 0, false,                STR_CONFIG_PATCHES_NEW_PATHFINDING_ALL,  NULL),
	SDT_BOOL(Patches, train_income_warn,             S, 0,  true,                STR_CONFIG_PATCHES_WARN_INCOME_LESS,     NULL),
	 SDT_VAR(Patches, order_review_system,SLE_UINT8, S,MS,     2,      0,     2, STR_CONFIG_PATCHES_ORDER_REVIEW,         NULL),
	SDT_BOOL(Patches, never_expire_vehicles,         0, 0, false,                STR_CONFIG_PATCHES_NEVER_EXPIRE_VEHICLES,NULL),
	 SDT_VAR(Patches, lost_train_days,   SLE_UINT16, S,D0,   180,    180,   720, STR_CONFIG_PATCHES_LOST_TRAIN_DAYS,      NULL),
	SDT_BOOL(Patches, autorenew,                     S, 0, false,                STR_CONFIG_PATCHES_AUTORENEW_VEHICLE,    NULL),//&EngineRenewUpdate),
	 SDT_VAR(Patches, autorenew_months,   SLE_INT16, S, 0,     6,    -12,    12, STR_CONFIG_PATCHES_AUTORENEW_MONTHS,     NULL),//&EngineRenewMonthsUpdate),
	 SDT_VAR(Patches, autorenew_money,     SLE_UINT, S,CR,100000,     0,2000000, STR_CONFIG_PATCHES_AUTORENEW_MONEY,      NULL),//&EngineRenewMoneyUpdate),
	 SDT_VAR(Patches, max_trains,        SLE_UINT16, 0, 0,   500,     0,   5000, STR_CONFIG_PATCHES_MAX_TRAINS,           NULL),
	 SDT_VAR(Patches, max_roadveh,       SLE_UINT16, 0, 0,   500,     0,   5000, STR_CONFIG_PATCHES_MAX_ROADVEH,          NULL),
	 SDT_VAR(Patches, max_aircraft,      SLE_UINT16, 0, 0,   200,     0,   5000, STR_CONFIG_PATCHES_MAX_AIRCRAFT,         NULL),
	 SDT_VAR(Patches, max_ships,         SLE_UINT16, 0, 0,   300,     0,   5000, STR_CONFIG_PATCHES_MAX_SHIPS,            NULL),
	SDT_BOOL(Patches, servint_ispercent,             0, 0, false,                STR_CONFIG_PATCHES_SERVINT_ISPERCENT,    NULL),//&CheckInterval),
	 SDT_VAR(Patches, servint_trains,    SLE_UINT16, 0,D0,   150,     5,    800, STR_CONFIG_PATCHES_SERVINT_TRAINS,       NULL),//&InValidateDetailsWindow),
	 SDT_VAR(Patches, servint_roadveh,   SLE_UINT16, 0,D0,   150,     5,    800, STR_CONFIG_PATCHES_SERVINT_ROADVEH,      NULL),//&InValidateDetailsWindow),
	 SDT_VAR(Patches, servint_ships,     SLE_UINT16, 0,D0,   360,     5,    800, STR_CONFIG_PATCHES_SERVINT_AIRCRAFT,     NULL),//&InValidateDetailsWindow),
	 SDT_VAR(Patches, servint_aircraft,  SLE_UINT16, 0,D0,   100,     5,    800, STR_CONFIG_PATCHES_SERVINT_SHIPS,        NULL),//&InValidateDetailsWindow),
	SDT_BOOL(Patches, no_servicing_if_no_breakdowns, 0, 0, false,                STR_CONFIG_PATCHES_NOSERVICE,            NULL),
	SDT_BOOL(Patches, wagon_speed_limits,            0, 0,  true,                STR_CONFIG_PATCHES_WAGONSPEEDLIMITS,     NULL),

	/***************************************************************************/
	/* Station section of the GUI-configure patches window (43 - 51) */
	SDT_BOOL(Patches, join_stations,           0, 0,  true,   STR_CONFIG_PATCHES_JOINSTATIONS,       NULL),
	SDT_BOOL(Patches, full_load_any,           0, 0,  true,   STR_CONFIG_PATCHES_FULLLOADANY,        NULL),
	SDT_BOOL(Patches, improved_load,           0, 0, false,   STR_CONFIG_PATCHES_IMPROVEDLOAD,       NULL),
	SDT_BOOL(Patches, selectgoods,             0, 0,  true,   STR_CONFIG_PATCHES_SELECTGOODS,        NULL),
	SDT_BOOL(Patches, new_nonstop,             0, 0, false,   STR_CONFIG_PATCHES_NEW_NONSTOP,        NULL),
	SDT_BOOL(Patches, nonuniform_stations,     0, 0,  true,   STR_CONFIG_PATCHES_NONUNIFORM_STATIONS,NULL),
	 SDT_VAR(Patches, station_spread,SLE_UINT8,0, 0, 12, 4,64,STR_CONFIG_PATCHES_STATION_SPREAD,     NULL),//&InvalidateStationBuildWindow),
	SDT_BOOL(Patches, serviceathelipad,        0, 0,  true,   STR_CONFIG_PATCHES_SERVICEATHELIPAD,   NULL),
	SDT_BOOL(Patches, modified_catchment,      0, 0,  true,   STR_CONFIG_PATCHES_CATCHMENT,          NULL),

	/***************************************************************************/
	/* Economy section of the GUI-configure patches window (52 - 62) */
	SDT_BOOL(Patches, inflation,                  0, 0,  true,            STR_CONFIG_PATCHES_INFLATION,        NULL),
	SDT_BOOL(Patches, build_rawmaterial_ind,      0, 0, false,            STR_CONFIG_PATCHES_BUILDXTRAIND,     NULL),
	SDT_BOOL(Patches, multiple_industry_per_town, 0, 0, false,            STR_CONFIG_PATCHES_MULTIPINDTOWN,    NULL),
	SDT_BOOL(Patches, same_industry_close,        0, 0, false,            STR_CONFIG_PATCHES_SAMEINDCLOSE,     NULL),
	SDT_BOOL(Patches, bribe,                      0, 0,  true,            STR_CONFIG_PATCHES_BRIBE,            NULL),
	 SDT_VAR(Patches, snow_line_height,SLE_UINT8, 0, 0,     7,   2,   13, STR_CONFIG_PATCHES_SNOWLINE_HEIGHT,  NULL),
	 SDT_VAR(Patches, colored_news_date,SLE_UINT, 0,NC,  2000,1900, 2200, STR_CONFIG_PATCHES_COLORED_NEWS_DATE,NULL),
	 SDT_VAR(Patches, starting_date,    SLE_UINT, 0,NC,  1950, MAX_YEAR_BEGIN_REAL, MAX_YEAR_END_REAL, STR_CONFIG_PATCHES_STARTING_DATE,NULL),
	 SDT_VAR(Patches, ending_date,      SLE_UINT,0,NC|NO,2051, MAX_YEAR_BEGIN_REAL, MAX_YEAR_END_REAL, STR_CONFIG_PATCHES_ENDING_DATE,  NULL),
	SDT_BOOL(Patches, smooth_economy,             0, 0,  true,            STR_CONFIG_PATCHES_SMOOTH_ECONOMY,   NULL),
	SDT_BOOL(Patches, allow_shares,               0, 0,  true,            STR_CONFIG_PATCHES_ALLOW_SHARES,     NULL),

	/***************************************************************************/
	/* AI section of the GUI-configure patches window (63 - 68) */
	SDT_BOOL(Patches, ainew_active,           0, 0, false, STR_CONFIG_PATCHES_AINEW_ACTIVE,      NULL),//&AiNew_PatchActive_Warning),
	SDT_BOOL(Patches, ai_in_multiplayer,      0, 0, false, STR_CONFIG_PATCHES_AI_IN_MULTIPLAYER, NULL),//&Ai_In_Multiplayer_Warning),
	SDT_BOOL(Patches, ai_disable_veh_train,   0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_TRAINS,  NULL),
	SDT_BOOL(Patches, ai_disable_veh_roadveh, 0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_ROADVEH, NULL),
	SDT_BOOL(Patches, ai_disable_veh_aircraft,0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_AIRCRAFT,NULL),
	SDT_BOOL(Patches, ai_disable_veh_ship,    0, 0, false, STR_CONFIG_PATCHES_AI_BUILDS_SHIPS,   NULL),

	/***************************************************************************/
	/* Patches without any GUI representation (69 - 78) */
	SDT_BOOL(Patches, keep_all_autosave,              S, 0, false,      STR_NULL, NULL),
	SDT_BOOL(Patches, autosave_on_exit,               S, 0, false,      STR_NULL, NULL),
	 SDT_VAR(Patches, max_num_autosaves,   SLE_UINT8, S, 0, 16, 0, 255, STR_NULL, NULL),
	SDT_BOOL(Patches, bridge_pillars,                 S, 0,  true,      STR_NULL, NULL),
	 SDT_VAR(Patches, extend_vehicle_life, SLE_UINT8, 0, 0,  0, 0, 100, STR_NULL, NULL),
	SDT_BOOL(Patches, auto_euro,                      S, 0,  true,      STR_NULL, NULL),
	 SDT_VAR(Patches, dist_local_authority,SLE_UINT8, 0, 0, 20, 5,  60, STR_NULL, NULL),
	 SDT_VAR(Patches, wait_oneway_signal,  SLE_UINT8, 0, 0, 15, 2, 100, STR_NULL, NULL),
	 SDT_VAR(Patches, wait_twoway_signal,  SLE_UINT8, 0, 0, 41, 2, 100, STR_NULL, NULL),

	/***************************************************************************/
	/* New Pathfinding patch settings (79 - 93) */
	SDT_VAR(Patches, pf_maxlength,      SLE_UINT16, 0, 0, 4096, 64, 65535, STR_NULL, NULL),
	SDT_VAR(Patches, pf_maxdepth,        SLE_UINT8, 0, 0,   48,  4,   255, STR_NULL, NULL),
	/* The maximum number of nodes to search */
	SDT_VAR(Patches, npf_max_search_nodes,SLE_UINT, 0, 0,10000,500,100000, STR_NULL, 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. */
	SDT_VAR(Patches, npf_rail_firstred_penalty,     SLE_UINT, 0, 0, (10 * NPF_TILE_LENGTH), 0, 100000, STR_NULL, NULL),
	SDT_VAR(Patches, npf_rail_firstred_exit_penalty,SLE_UINT, 0, 0, (100 * NPF_TILE_LENGTH),0, 100000, STR_NULL, 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. */
	SDT_VAR(Patches, npf_rail_lastred_penalty, SLE_UINT, 0, 0, (10 * NPF_TILE_LENGTH), 0, 100000, STR_NULL, 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. */
	SDT_VAR(Patches, npf_rail_station_penalty, SLE_UINT, 0, 0, (1 * NPF_TILE_LENGTH), 0, 100000, STR_NULL, NULL),
	SDT_VAR(Patches, npf_rail_slope_penalty,   SLE_UINT, 0, 0, (1 * NPF_TILE_LENGTH), 0, 100000, STR_NULL, 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 */
	SDT_VAR(Patches, npf_rail_curve_penalty,        SLE_UINT, 0, 0, 1,                      0, 100000, STR_NULL, 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? */
	SDT_VAR(Patches, npf_rail_depot_reverse_penalty,SLE_UINT, 0, 0, (NPF_TILE_LENGTH * 50), 0, 100000, STR_NULL, NULL),
	SDT_VAR(Patches, npf_buoy_penalty,              SLE_UINT, 0, 0, (2 * NPF_TILE_LENGTH),  0, 100000, STR_NULL, 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 */
	SDT_VAR(Patches, npf_water_curve_penalty,       SLE_UINT, 0, 0, (NPF_TILE_LENGTH / 4),  0, 100000, STR_NULL, NULL),
	/* This is the penalty for road, same as for rail. */
	SDT_VAR(Patches, npf_road_curve_penalty,        SLE_UINT, 0, 0, 1,                      0, 100000, STR_NULL, NULL),
	/* This is the penalty for level crossings, for both road and rail vehicles */
	SDT_VAR(Patches, npf_crossing_penalty,          SLE_UINT, 0, 0, (3 * NPF_TILE_LENGTH),  0, 100000, STR_NULL, NULL),

	SDT_END()
};

static const SettingDesc _currency_settings[] = {
	SDT_VAR(CurrencySpec, rate,    SLE_UINT16, S, 0,  1, 0, 100, STR_NULL, NULL),
	SDT_CHR(CurrencySpec, separator,           S, 0,        '.', STR_NULL, NULL),
	SDT_VAR(CurrencySpec, to_euro, SLE_UINT16, S, 0,  0, 0,1000, STR_NULL, NULL),
	SDT_STR(CurrencySpec, prefix,    SLE_STRQ, S, 0,       NULL, STR_NULL, NULL),
	SDT_STR(CurrencySpec, suffix,    SLE_STRQ, S, 0, " credits", STR_NULL, NULL),
	SDT_END()
};

/* Undefine for the shortcut macros above */
#undef S
#undef C
#undef N

#undef D0
#undef NC
#undef MS
#undef NO
#undef CR

typedef void SettingDescProc(IniFile *ini, const SettingDesc *desc, const char *grpname, void *object);
typedef void SettingDescProcList(IniFile *ini, const char *grpname, char **list, uint len);

/* Common handler for saving/loading variables to the configuration file */
static void HandleSettingDescs(IniFile *ini, SettingDescProc *proc, SettingDescProcList *proc_list)
{
	proc(ini, (const SettingDesc*)_misc_settings,    "misc",  NULL);
	proc(ini, (const SettingDesc*)_music_settings,   "music", &msf);
#ifdef WIN32
	proc(ini, (const SettingDesc*)_win32_settings,   "win32", NULL);
#endif /* WIN32 */

	proc(ini, _gameopt_settings, "gameopt",  &_opt_newgame);
	proc(ini, _patch_settings,   "patches",  &_patches);
	proc(ini, _currency_settings,"currency", &_custom_currency);

#ifdef ENABLE_NETWORK
	proc(ini, (const SettingDesc*)_network_settings, "network", NULL);
	proc_list(ini, "servers", _network_host_list, lengthof(_network_host_list));
	proc_list(ini, "bans",    _network_ban_list,  lengthof(_network_ban_list));
#endif /* ENABLE_NETWORK */
}

/** Load the values from the configuration files */
void LoadFromConfig(void)
{
	IniFile *ini = ini_load(_config_file);
	HandleSettingDescs(ini, ini_load_settings, ini_load_setting_list);
	ini_load_setting_list(ini, "newgrf", _newgrf_files, lengthof(_newgrf_files));
	ini_free(ini);
}

/** Save the values to the configuration file */
void SaveToConfig(void)
{
	IniFile *ini = ini_load(_config_file);
	HandleSettingDescs(ini, ini_save_settings, ini_save_setting_list);
	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;
	}
}

const ChunkHandler _setting_chunk_handlers[] = {
	{ 'OPTS', SaveLoad_OPTS, SaveLoad_OPTS, CH_RIFF | CH_LAST}
};