settings.c
changeset 0 29654efe3188
child 11 836bc4b37b5b
equal deleted inserted replaced
-1:000000000000 0:29654efe3188
       
     1 #include "stdafx.h"
       
     2 #include "ttd.h"
       
     3 #include "sound.h"
       
     4 
       
     5 enum SettingDescType {
       
     6 	SDT_INTX, // must be 0
       
     7 	SDT_ONEOFMANY,
       
     8 	SDT_MANYOFMANY,
       
     9 	SDT_BOOLX,
       
    10 	SDT_STRING,
       
    11 	SDT_STRINGBUF,
       
    12 	SDT_INTLIST,
       
    13 
       
    14 	SDT_INT8 = 0 << 4,
       
    15 	SDT_UINT8 = 1 << 4,
       
    16 	SDT_INT16 = 2 << 4,
       
    17 	SDT_UINT16 = 3 << 4,
       
    18 	SDT_INT32 = 4 << 4,
       
    19 	SDT_UINT32 = 5 << 4,
       
    20 	SDT_CALLBX = 6 << 4,
       
    21 
       
    22 	SDT_UINT = SDT_UINT32,
       
    23 	SDT_INT = SDT_INT32,
       
    24 
       
    25 	SDT_NOSAVE = 1 << 8,
       
    26 
       
    27 	SDT_CALLB = SDT_INTX | SDT_CALLBX,
       
    28 
       
    29 	SDT_BOOL = SDT_BOOLX | SDT_UINT8,
       
    30 };
       
    31 
       
    32 typedef struct IniFile IniFile;
       
    33 typedef struct IniItem IniItem;
       
    34 typedef struct IniGroup IniGroup;
       
    35 typedef struct SettingDesc SettingDesc;
       
    36 typedef struct MemoryPool MemoryPool;
       
    37 
       
    38 static void pool_init(MemoryPool **pool);
       
    39 static void *pool_alloc(MemoryPool **pool, uint size);
       
    40 static void *pool_strdup(MemoryPool **pool, const char *mem, uint size);
       
    41 static void pool_free(MemoryPool **pool);
       
    42 
       
    43 struct MemoryPool {
       
    44 	uint pos,size;
       
    45 	MemoryPool *next;
       
    46 	byte mem[1];
       
    47 };
       
    48 
       
    49 static MemoryPool *pool_new(uint minsize)
       
    50 {
       
    51 	MemoryPool *p;
       
    52 	if (minsize < 4096 - 12) minsize = 4096 - 12;
       
    53 	
       
    54 	p = malloc(sizeof(MemoryPool) - 1 + minsize);
       
    55 	p->pos = 0;
       
    56 	p->size = minsize;
       
    57 	p->next = NULL;
       
    58 	return p;
       
    59 }
       
    60 
       
    61 static void pool_init(MemoryPool **pool)
       
    62 {
       
    63 	*pool = pool_new(0);
       
    64 }
       
    65 
       
    66 static void *pool_alloc(MemoryPool **pool, uint size)
       
    67 {
       
    68 	uint pos;
       
    69 	MemoryPool *p = *pool;
       
    70 
       
    71 	size = (size + 3) & ~3; // align everything to a 32 bit boundary
       
    72 
       
    73 	// first check if there's memory in the next pool
       
    74 	if (p->next && p->next->pos + size <= p->next->size) {
       
    75 		p = p->next;
       
    76 	// then check if there's not memory in the cur pool
       
    77 	} else if (p->pos + size > p->size) {
       
    78 		MemoryPool *n = pool_new(size);
       
    79 		*pool = n;
       
    80 		n->next = p;
       
    81 		p = n;		
       
    82 	}
       
    83 
       
    84 	pos = p->pos;
       
    85 	p->pos += size;
       
    86 	return p->mem + pos;
       
    87 }
       
    88 
       
    89 static void *pool_strdup(MemoryPool **pool, const char *mem, uint size)
       
    90 {
       
    91 	byte *p = pool_alloc(pool, size + 1);
       
    92 	p[size] = 0;
       
    93 	memcpy(p, mem, size);
       
    94 	return p;
       
    95 }
       
    96 
       
    97 static void pool_free(MemoryPool **pool)
       
    98 {
       
    99 	MemoryPool *p = *pool, *n;
       
   100 	*pool = NULL;
       
   101 	while (p) {
       
   102 		n = p->next;
       
   103 		free(p);
       
   104 		p = n;
       
   105 	}
       
   106 }
       
   107 
       
   108 // structs describing the ini format.
       
   109 struct IniItem {
       
   110 	char *name;
       
   111 	char *value;
       
   112 	char *comment;
       
   113 	IniItem *next;
       
   114 };
       
   115 
       
   116 struct IniGroup {
       
   117 	char *name; // name of group
       
   118 	char *comment; //comment for group
       
   119 	IniItem *item, **last_item;
       
   120 	IniGroup *next;
       
   121 	IniFile *ini;
       
   122 };
       
   123 
       
   124 struct IniFile {
       
   125 	MemoryPool *pool;
       
   126 	IniGroup *group, **last_group;
       
   127 	char *comment; // last comment in file
       
   128 };
       
   129 
       
   130 // allocate an inifile object
       
   131 static IniFile *ini_alloc()
       
   132 {
       
   133 	IniFile *ini;
       
   134 	MemoryPool *pool;
       
   135 	pool_init(&pool);
       
   136 	ini = (IniFile*)pool_alloc(&pool, sizeof(IniFile));
       
   137 	ini->pool = pool;
       
   138 	ini->group = NULL;
       
   139 	ini->last_group = &ini->group;
       
   140 	ini->comment = NULL;
       
   141 	return ini;
       
   142 }
       
   143 
       
   144 // allocate an ini group object
       
   145 static IniGroup *ini_group_alloc(IniFile *ini, const char *grpt, int len)
       
   146 {
       
   147 	IniGroup *grp = pool_alloc(&ini->pool, sizeof(IniGroup));
       
   148 	grp->ini = ini;
       
   149 	grp->name = pool_strdup(&ini->pool, grpt, len);
       
   150 	grp->next = NULL;
       
   151 	grp->item = NULL;
       
   152 	grp->comment = NULL;
       
   153 	grp->last_item = &grp->item;
       
   154 	*ini->last_group = grp;
       
   155 	ini->last_group = &grp->next;
       
   156 	return grp;
       
   157 }
       
   158 
       
   159 static IniItem *ini_item_alloc(IniGroup *group, const char *name, int len)
       
   160 {
       
   161 	IniItem *item = pool_alloc(&group->ini->pool, sizeof(IniItem));
       
   162 	item->name = pool_strdup(&group->ini->pool, name, len);
       
   163 	item->next = NULL;
       
   164 	item->comment = NULL;
       
   165 	item->value = NULL;
       
   166 	*group->last_item = item;
       
   167 	group->last_item = &item->next;
       
   168 	return item;
       
   169 }
       
   170 
       
   171 // load an ini file into the "abstract" format
       
   172 static IniFile *ini_load(const char *filename)
       
   173 {
       
   174 	char buffer[1024], c, *s, *t, *e;
       
   175 	FILE *in;
       
   176 	IniFile *ini;
       
   177 	IniGroup *group = NULL;
       
   178 	IniItem *item;
       
   179 
       
   180 	byte *comment = NULL;
       
   181 	uint comment_size = 0;
       
   182 	uint comment_alloc = 0;
       
   183 
       
   184 	ini = ini_alloc();
       
   185 
       
   186 	in = fopen(filename, "r");
       
   187 	if (in == NULL) return ini;
       
   188 
       
   189 	// for each line in the file
       
   190 	while (fgets(buffer, sizeof(buffer), in)) {
       
   191 		
       
   192 		// trim whitespace from the left side
       
   193 		for(s=buffer; *s == ' ' || *s == '\t'; s++);
       
   194 
       
   195 		// trim whitespace from right side.
       
   196 		e = s + strlen(s);
       
   197 		while (e > s && ((c=e[-1]) == '\n' || c == '\r' || c == ' ' || c == '\t')) e--;
       
   198 		*e = 0;
       
   199 
       
   200 		// skip comments and empty lines
       
   201 		if (*s == '#' || *s == 0) {
       
   202 			uint ns = comment_size + (e - s + 1);
       
   203 			uint a = comment_alloc;
       
   204 			uint pos;
       
   205 			// add to comment
       
   206 			if (ns > a) {
       
   207 				a = max(a, 128);
       
   208 				do a*=2; while (a < ns);
       
   209 				comment = realloc(comment, comment_alloc = a);
       
   210 			}
       
   211 			pos = comment_size;
       
   212 			comment_size += (e - s + 1);
       
   213 			comment[pos + e - s] = '\n'; // comment newline
       
   214 			memcpy(comment + pos, s, e - s); // copy comment contents
       
   215 			continue;
       
   216 		}
       
   217 
       
   218 		// it's a group?
       
   219 		if (s[0] == '[') {
       
   220 			if (e[-1] != ']')
       
   221 				ShowInfoF("ini: invalid group name '%s'\n", buffer);
       
   222 			else
       
   223 				e--;
       
   224 			s++; // skip [
       
   225 			group = ini_group_alloc(ini, s, e - s);
       
   226 			if (comment_size) {
       
   227 				group->comment = pool_strdup(&ini->pool, comment, comment_size);
       
   228 				comment_size = 0;
       
   229 			}
       
   230 		} else if (group) {
       
   231 			// find end of keyname
       
   232 			for(t=s; *t != 0 && *t != '=' && *t != '\t' && *t != ' '; t++) {}
       
   233 						
       
   234 			// it's an item in an existing group
       
   235 			item = ini_item_alloc(group, s, t-s);
       
   236 			if (comment_size) {
       
   237 				item->comment = pool_strdup(&ini->pool, comment, comment_size);
       
   238 				comment_size = 0;
       
   239 			}
       
   240 
       
   241 			// find start of parameter
       
   242 			while (*t == '=' || *t == ' ' || *t == '\t') t++;
       
   243 			item->value = pool_strdup(&ini->pool, t, e - t);
       
   244 		} else {
       
   245 			// it's an orphan item
       
   246 			ShowInfoF("ini: '%s' outside of group\n", buffer);
       
   247 		}
       
   248 	}
       
   249 
       
   250 	if (comment_size) {
       
   251 		ini->comment = pool_strdup(&ini->pool, comment, comment_size);
       
   252 		comment_size = 0;
       
   253 	}
       
   254 
       
   255 	free(comment);
       
   256 	fclose(in);
       
   257 
       
   258 	return ini;
       
   259 }
       
   260 
       
   261 // lookup a group or make a new one
       
   262 static IniGroup *ini_getgroup(IniFile *ini, const char *name, int len)
       
   263 {
       
   264 	IniGroup *group;
       
   265 
       
   266 	if (len == -1) len = strlen(name);
       
   267 
       
   268 	// does it exist already?
       
   269 	for(group = ini->group; group; group = group->next)
       
   270 		if (!memcmp(group->name, name, len) && group->name[len] == 0)
       
   271 			return group;
       
   272 
       
   273 	// otherwise make a new one
       
   274 	group = ini_group_alloc(ini, name, len);
       
   275 	group->comment = pool_strdup(&ini->pool, "\n", 1);
       
   276 	return group;
       
   277 }
       
   278 
       
   279 // lookup an item or make a new one
       
   280 static IniItem *ini_getitem(IniGroup *group, const char *name, bool create)
       
   281 {
       
   282 	IniItem *item;
       
   283 	uint len = strlen(name);
       
   284 
       
   285 	for(item = group->item; item; item = item->next)
       
   286 		if (!strcmp(item->name, name))
       
   287 			return item;
       
   288 	
       
   289 	if (!create) return NULL;
       
   290 
       
   291 	// otherwise make a new one
       
   292 	item = ini_item_alloc(group, name, len);
       
   293 	return item;
       
   294 }
       
   295 
       
   296 // save ini file from the "abstract" format.
       
   297 static bool ini_save(const char *filename, IniFile *ini)
       
   298 {
       
   299 	FILE *f;
       
   300 	IniGroup *group;
       
   301 	IniItem *item;
       
   302 	
       
   303 	f = fopen(filename, "w");
       
   304 	if (f == NULL) return false;
       
   305 
       
   306 	for(group = ini->group; group; group = group->next) {
       
   307 		if (group->comment) fputs(group->comment, f);
       
   308 		fprintf(f, "[%s]\n", group->name);
       
   309 		for(item = group->item; item; item = item->next) {
       
   310 			if (item->comment) fputs(item->comment, f);
       
   311 			fprintf(f, "%s = %s\n", item->name, item->value ? item->value : "");
       
   312 		}
       
   313 	}
       
   314 	if (ini->comment) fputs(ini->comment, f);
       
   315 
       
   316 	fclose(f);
       
   317 	return true;
       
   318 }
       
   319 
       
   320 static void ini_free(IniFile *ini)
       
   321 {
       
   322 	pool_free(&ini->pool);
       
   323 }
       
   324 
       
   325 struct SettingDesc {
       
   326 	const char *name;
       
   327 	int flags;
       
   328 	void *def;
       
   329 	void *ptr;
       
   330 	void *b;
       
   331 	
       
   332 };
       
   333 
       
   334 static int lookup_oneofmany(const char *many, const char *one, int onelen)
       
   335 {
       
   336 	const char *s;
       
   337 	int idx;
       
   338 
       
   339 	if (onelen == -1) onelen = strlen(one);
       
   340 
       
   341 	// check if it's an integer
       
   342 	if (*one >= '0' && *one <= '9')
       
   343 		return strtoul(one, NULL, 0);
       
   344 		
       
   345 	idx = 0;
       
   346 	for(;;) {
       
   347 		// find end of item
       
   348 		s = many;
       
   349 		while (*s != '|' && *s != 0) s++;
       
   350 		if (s - many == onelen && !memcmp(one, many, onelen)) return idx;
       
   351 		if (*s == 0) return -1;
       
   352 		many = s + 1;
       
   353 		idx++;
       
   354 	}
       
   355 }
       
   356 
       
   357 static uint32 lookup_manyofmany(const char *many, const char *str)
       
   358 {
       
   359 	const char *s;
       
   360 	int r;
       
   361 	uint32 res = 0;
       
   362 
       
   363 	for(;;) {
       
   364 		// skip "whitespace"
       
   365 		while (*str == ' ' || *str == '\t' || *str == '|') str++;
       
   366 		if (*str == 0) break;
       
   367 
       
   368 		s = str;
       
   369 		while (*s != 0 && *s != ' ' && *s != '\t' && *s != '|') s++;
       
   370 
       
   371 		r = lookup_oneofmany(many, str, s - str);
       
   372 		if (r == -1) return (uint32)-1;
       
   373 
       
   374 		res |= (1 << r);
       
   375 		if (*s == 0) break;
       
   376 		str = s + 1;
       
   377 	}
       
   378 	return res;
       
   379 }
       
   380 
       
   381 static int parse_intlist(const char *p, int *items, int maxitems)
       
   382 {
       
   383 	int n = 0, v;
       
   384 	char *end;
       
   385 
       
   386 	for(;;) {
       
   387 		v = strtol(p, &end, 0);
       
   388 		if (p == end || n == maxitems) return -1;
       
   389 		p = end;
       
   390 		items[n++] = v;
       
   391 		if (*p == 0) break;
       
   392 		if (*p != ',') return -1;
       
   393 		p++;
       
   394 	}
       
   395 
       
   396 	return n;
       
   397 }
       
   398 
       
   399 static bool load_intlist(const char *str, void *array, int nelems, int type)
       
   400 {
       
   401 	int items[64];
       
   402 	int i,nitems;
       
   403 	
       
   404 	if (str == NULL) {
       
   405 		memset(items, 0, sizeof(items));
       
   406 		nitems = nelems;
       
   407 	} else {
       
   408 		nitems = parse_intlist(str, items, lengthof(items));
       
   409 		if (nitems != nelems)
       
   410 			return false;
       
   411 	}
       
   412 
       
   413 	switch(type) {
       
   414 	case SDT_INT8 >> 4:
       
   415 	case SDT_UINT8 >> 4:
       
   416 		for(i=0; i!=nitems; i++) ((byte*)array)[i] = items[i];
       
   417 		break;
       
   418 	case SDT_INT16 >> 4:
       
   419 	case SDT_UINT16 >> 4:
       
   420 		for(i=0; i!=nitems; i++) ((uint16*)array)[i] = items[i];
       
   421 		break;
       
   422 	case SDT_INT32 >> 4:
       
   423 	case SDT_UINT32 >> 4:
       
   424 		for(i=0; i!=nitems; i++) ((uint32*)array)[i] = items[i];
       
   425 		break;
       
   426 	default:
       
   427 		NOT_REACHED();
       
   428 	}
       
   429 
       
   430 	return true;
       
   431 }
       
   432 
       
   433 static void make_intlist(char *buf, void *array, int nelems, int type)
       
   434 {
       
   435 	int i, v = 0;
       
   436 	byte *p = (byte*)array;
       
   437 	for(i=0; i!=nelems; i++) {
       
   438 		switch(type) {
       
   439 		case SDT_INT8 >> 4: v = *(int8*)p; p += 1; break;
       
   440 		case SDT_UINT8 >> 4:v = *(byte*)p; p += 1; break;
       
   441 		case SDT_INT16 >> 4:v = *(int16*)p; p += 2; break;
       
   442 		case SDT_UINT16 >> 4:v = *(uint16*)p; p += 2; break;
       
   443 		case SDT_INT32 >> 4:v = *(int32*)p; p += 4; break;
       
   444 		case SDT_UINT32 >> 4:v = *(uint32*)p; p += 4; break;
       
   445 		default: NOT_REACHED();
       
   446 		}
       
   447 		buf += sprintf(buf, i ? ",%d" : "%d", v);
       
   448 	}
       
   449 }
       
   450 
       
   451 static void make_oneofmany(char *buf, const char *many, int i)
       
   452 {
       
   453 	int orig_i = i;
       
   454 	char c;
       
   455 
       
   456 	while (--i >= 0) {
       
   457 		do {
       
   458 			many++;
       
   459 			if (many[-1] == 0) {
       
   460 				sprintf(buf, "%d", orig_i);
       
   461 				return;
       
   462 			}
       
   463 		} while (many[-1] != '|');
       
   464 	}
       
   465 
       
   466 	// copy until | or 0
       
   467 	while ((c=*many++) != 0 && c != '|')
       
   468 		*buf++ = c;
       
   469 	*buf = 0;
       
   470 }
       
   471 
       
   472 static void make_manyofmany(char *buf, const char *many, uint32 x)
       
   473 {
       
   474 	const char *start;
       
   475 	int i = 0;
       
   476 	bool init = true;
       
   477 
       
   478 	do {
       
   479 		start = many;
       
   480 		while (*many != 0 && *many != '|') many++;
       
   481 		if (x & 1) {
       
   482 			if (!init) *buf++ = '|';
       
   483 			init = false;
       
   484 			if (start == many) {
       
   485 				buf += sprintf(buf, "%d", i);
       
   486 			} else {
       
   487 				memcpy(buf, start, many - start);
       
   488 				buf += many - start;
       
   489 			}
       
   490 		}
       
   491 		if (*many == '|') many++;
       
   492 	} while (++i, x>>=1);
       
   493 	*buf = 0;
       
   494 }
       
   495 
       
   496 static void *string_to_val(const SettingDesc *desc, const char *str)
       
   497 {
       
   498 	unsigned long val;
       
   499 	char *end;
       
   500 
       
   501 	switch(desc->flags & 0xF) {
       
   502 	case SDT_INTX:
       
   503 		val = strtol(str, &end, 0);
       
   504 		if (*end != 0) ShowInfoF("ini: trailing characters at end of setting '%s'", desc->name);
       
   505 		return (void*)val;
       
   506 	case SDT_ONEOFMANY: {
       
   507 		int r = lookup_oneofmany((char*)desc->b, str, -1);
       
   508 		if (r != -1) return (void*)r;
       
   509 		ShowInfoF("ini: invalid value '%s' for '%s'", str, desc->name);
       
   510 		return 0;
       
   511 	}
       
   512 	case SDT_MANYOFMANY: {
       
   513 		uint32 r = lookup_manyofmany(desc->b, str);
       
   514 		if (r != (uint32)-1) return (void*)r;
       
   515 		ShowInfoF("ini: invalid value '%s' for '%s'", str, desc->name);
       
   516 		return 0;
       
   517 	}
       
   518 	case SDT_BOOLX:
       
   519 		if (!strcmp(str, "true") || !strcmp(str, "on") || !strcmp(str, "1"))
       
   520 			return (void*)true;
       
   521 		if (!strcmp(str, "false") || !strcmp(str, "off") || !strcmp(str, "0"))
       
   522 			return (void*)false;
       
   523 		ShowInfoF("ini: invalid setting value '%s' for '%s'", str, desc->name);
       
   524 		break;
       
   525 	
       
   526 	case SDT_STRING:
       
   527 	case SDT_STRINGBUF:
       
   528 	case SDT_INTLIST:
       
   529 		return (void*)str;
       
   530 	}
       
   531 
       
   532 	return NULL;
       
   533 }
       
   534 
       
   535 static void load_setting_desc(IniFile *ini, const SettingDesc *desc, void *grpname, void *base)
       
   536 {
       
   537 	IniGroup *group_def = ini_getgroup(ini, grpname, -1), *group;
       
   538 	IniItem *item;
       
   539 	void *p;
       
   540 	void *ptr;
       
   541 
       
   542 	for (;desc->name;desc++) {
       
   543 		// group override?
       
   544 		const char *s = strchr(desc->name, '.');
       
   545 		if (s) {
       
   546 			group = ini_getgroup(ini, desc->name, s - desc->name);
       
   547 			s++;
       
   548 		} else {
       
   549 			s = desc->name;
       
   550 			group = group_def;
       
   551 		}
       
   552 		
       
   553 		item = ini_getitem(group, s, false);
       
   554 		if (!item) {
       
   555 			p = desc->def;
       
   556 		} else {
       
   557 			p = string_to_val(desc, item->value);
       
   558 		}
       
   559 		
       
   560 		// get ptr to array
       
   561 		ptr = desc->ptr;
       
   562 		if ( (uint32)ptr < 0x10000)
       
   563 			ptr = (byte*)base + (uint32)ptr;
       
   564 
       
   565 		switch(desc->flags & 0xF) {
       
   566 		// all these are stored in the same way
       
   567 		case SDT_INTX:
       
   568 		case SDT_ONEOFMANY:
       
   569 		case SDT_MANYOFMANY:
       
   570 		case SDT_BOOLX:
       
   571 			switch(desc->flags >> 4 & 7) {
       
   572 			case SDT_INT8 >> 4:
       
   573 			case SDT_UINT8 >> 4:
       
   574 				*(byte*)ptr = (byte)(uint)p;
       
   575 				break;
       
   576 			case SDT_INT16 >> 4:
       
   577 			case SDT_UINT16 >> 4:
       
   578 				*(uint16*)ptr = (uint16)(uint)p;
       
   579 				break;
       
   580 			case SDT_INT32 >> 4:
       
   581 			case SDT_UINT32 >> 4:
       
   582 				*(uint32*)ptr = (uint32)p;
       
   583 				break;
       
   584 			default:
       
   585 				NOT_REACHED();
       
   586 			}
       
   587 			break;
       
   588 		case SDT_STRING:
       
   589 			if (*(char**)ptr) free(*(char**)ptr);
       
   590 			*(char**)ptr = strdup((char*)p);
       
   591 			break;
       
   592 		case SDT_STRINGBUF:
       
   593 			if (p) ttd_strlcpy((char*)ptr, p, desc->flags >> 16);
       
   594 			break;
       
   595 		case SDT_INTLIST: {
       
   596 			if (!load_intlist(p, ptr, desc->flags >> 16, desc->flags >> 4 & 7))
       
   597 				ShowInfoF("ini: error in array '%s'", desc->name);
       
   598 			break;
       
   599 		}
       
   600 		default:
       
   601 			NOT_REACHED();
       
   602 		}
       
   603 	}	
       
   604 }
       
   605 
       
   606 static void save_setting_desc(IniFile *ini, const SettingDesc *desc, void *grpname, void *base)
       
   607 {
       
   608 	IniGroup *group_def = NULL, *group;
       
   609 	IniItem *item;
       
   610 	void *p, *ptr;
       
   611 	int i = 0;
       
   612 	char buf[512]; // setting buffer
       
   613 	const char *s;
       
   614 
       
   615 	for (;desc->name;desc++) {
       
   616 		if (desc->flags & SDT_NOSAVE)
       
   617 			continue;
       
   618 		
       
   619 		// group override?
       
   620 		s = strchr(desc->name, '.');
       
   621 		if (s) {
       
   622 			group = ini_getgroup(ini, desc->name, s - desc->name);
       
   623 			s++;
       
   624 		} else {
       
   625 			if (group_def == NULL)
       
   626 				group_def = ini_getgroup(ini, grpname, -1);
       
   627 			s = desc->name;
       
   628 			group = group_def;
       
   629 		}
       
   630 		
       
   631 		item = ini_getitem(group, s, true);
       
   632 
       
   633 		// get ptr to array
       
   634 		ptr = desc->ptr;
       
   635 		if ( (uint32)ptr < 0x10000)
       
   636 			ptr = (byte*)base + (uint32)ptr;
       
   637 		
       
   638 		if (item->value != NULL) {
       
   639 			// check if the value is the same as the old value	
       
   640 			p = string_to_val(desc, item->value);
       
   641 
       
   642 			switch(desc->flags & 0xF) {
       
   643 			case SDT_INTX:
       
   644 			case SDT_ONEOFMANY:
       
   645 			case SDT_MANYOFMANY:
       
   646 			case SDT_BOOLX:
       
   647 				switch(desc->flags >> 4 & 7) {
       
   648 				case SDT_INT8 >> 4:
       
   649 				case SDT_UINT8 >> 4:
       
   650 					if (*(byte*)ptr == (byte)(uint)p)
       
   651 						continue;
       
   652 					break;
       
   653 				case SDT_INT16 >> 4:
       
   654 				case SDT_UINT16 >> 4:
       
   655 					if (*(uint16*)ptr == (uint16)(uint)p)
       
   656 						continue;
       
   657 					break;
       
   658 				case SDT_INT32 >> 4:
       
   659 				case SDT_UINT32 >> 4:
       
   660 					if (*(uint32*)ptr == (uint32)p)
       
   661 						continue;	
       
   662 					break;
       
   663 				default:
       
   664 					NOT_REACHED();
       
   665 				}
       
   666 				break;
       
   667 			case SDT_STRING:
       
   668 				assert(0);
       
   669 				break;
       
   670 			case SDT_INTLIST:
       
   671 				// assume intlist is always changed.
       
   672 				break;
       
   673 			}
       
   674 		}
       
   675 
       
   676 		switch(desc->flags & 0xF) {
       
   677 		case SDT_INTX:
       
   678 		case SDT_ONEOFMANY:
       
   679 		case SDT_MANYOFMANY:
       
   680 		case SDT_BOOLX:
       
   681 			switch(desc->flags >> 4 & 7) {
       
   682 			case SDT_INT8 >> 4: i = *(int8*)ptr; break;
       
   683 			case SDT_UINT8 >> 4:i = *(byte*)ptr; break;
       
   684 			case SDT_INT16 >> 4:i = *(int16*)ptr; break;
       
   685 			case SDT_UINT16 >> 4:i = *(uint16*)ptr; break;
       
   686 			case SDT_INT32 >> 4:i = *(int32*)ptr; break;
       
   687 			case SDT_UINT32 >> 4:i = *(uint32*)ptr; break;
       
   688 			default:
       
   689 				NOT_REACHED();
       
   690 			}
       
   691 			switch(desc->flags & 0xF) {
       
   692 			case SDT_INTX:
       
   693 				sprintf(buf, "%d", i);
       
   694 				break;
       
   695 			case SDT_ONEOFMANY:
       
   696 				make_oneofmany(buf, (char*)desc->b, i);
       
   697 				break;
       
   698 			case SDT_MANYOFMANY:
       
   699 				make_manyofmany(buf, (char*)desc->b, i);
       
   700 				break;
       
   701 			case SDT_BOOLX:
       
   702 				strcpy(buf, i ? "true" : "false");
       
   703 				break;
       
   704 			default:
       
   705 				NOT_REACHED();
       
   706 			}
       
   707 			break;
       
   708 		case SDT_STRINGBUF:
       
   709 			strcpy(buf, (char*)ptr);
       
   710 			break;
       
   711 		case SDT_STRING:
       
   712 			strcpy(buf, *(char**)ptr);
       
   713 			break;
       
   714 		case SDT_INTLIST:
       
   715 			make_intlist(buf, ptr, desc->flags >> 16, desc->flags >> 4 & 7);
       
   716 			break;
       
   717 		}
       
   718 		// the value is different, that means we have to write it to the ini
       
   719 		item->value = pool_strdup(&ini->pool, buf, strlen(buf));
       
   720 	}	
       
   721 }
       
   722 
       
   723 //***************************
       
   724 // TTD specific INI stuff
       
   725 //***************************
       
   726 
       
   727 static const SettingDesc music_settings[] = {
       
   728 	{"playlist", SDT_UINT8, (void*)0, (void*)offsetof(MusicFileSettings, playlist) },
       
   729 	{"music_vol", SDT_UINT8, (void*)128, (void*)offsetof(MusicFileSettings, music_vol) },
       
   730 	{"effect_vol", SDT_UINT8, (void*)128, (void*)offsetof(MusicFileSettings, effect_vol) },
       
   731 	{"custom_1", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_1) << 16, NULL, (void*)offsetof(MusicFileSettings, custom_1) },
       
   732 	{"custom_2", SDT_INTLIST | SDT_UINT8 | lengthof(msf.custom_2) << 16, NULL, (void*)offsetof(MusicFileSettings, custom_2) },
       
   733 	{"playing", SDT_BOOL, (void*)true, (void*)offsetof(MusicFileSettings, btn_down) },
       
   734 	{"shuffle", SDT_BOOL, (void*)false, (void*)offsetof(MusicFileSettings, shuffle) },
       
   735 	{NULL}
       
   736 };
       
   737 
       
   738 static const SettingDesc win32_settings[] = {
       
   739 	{"display_hz", SDT_UINT, (void*)0, &_display_hz},
       
   740 	{"force_full_redraw", SDT_BOOL, (void*)false, &_force_full_redraw},
       
   741 	{"fullscreen_bpp", SDT_UINT, (void*)8, &_fullscreen_bpp},
       
   742 	{"double_size", SDT_BOOL, (void*)false, &_double_size},
       
   743 	{NULL}
       
   744 };
       
   745 
       
   746 static const SettingDesc misc_settings[] = {
       
   747 	{"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_CHECKPOINTS), &_display_opt, "SHOW_TOWN_NAMES|SHOW_STATION_NAMES|SHOW_SIGNS|FULL_ANIMATION|TRANS_BUILDINGS|FULL_DETAIL|CHECKPOINTS"},
       
   748 	{"news_display_opt", SDT_UINT16, (void*)-1, &_news_display_opt},
       
   749 	{"fullscreen", SDT_BOOL, (void*)false, &_fullscreen},
       
   750 	{"videodriver", SDT_STRINGBUF | (lengthof(_ini_videodriver)<<16) | SDT_NOSAVE, NULL, _ini_videodriver},
       
   751 	{"musicdriver", SDT_STRINGBUF | (lengthof(_ini_musicdriver)<<16) | SDT_NOSAVE, NULL, _ini_musicdriver},
       
   752 	{"sounddriver", SDT_STRINGBUF | (lengthof(_ini_sounddriver)<<16) | SDT_NOSAVE, NULL, _ini_sounddriver},
       
   753 	{"language", SDT_STRINGBUF | lengthof(_dynlang.curr_file)<<16, NULL, _dynlang.curr_file },
       
   754 	{"resolution", SDT_UINT16 | SDT_INTLIST | lengthof(_cur_resolution) << 16, "640,480", _cur_resolution},
       
   755 	{"cache_sprites", SDT_BOOL, (void*)false, &_cache_sprites},
       
   756 	{"screenshot_format", SDT_STRINGBUF | (lengthof(_screenshot_format_name)<<16), NULL, _screenshot_format_name},
       
   757 	{"savegame_format", SDT_STRINGBUF | (lengthof(_savegame_format)<<16), NULL, _savegame_format},
       
   758 	{"rightclick_emulate", SDT_BOOL, (void*)false, &_rightclick_emulate},
       
   759 	{NULL}
       
   760 };
       
   761 
       
   762 static const SettingDesc network_settings[] = {
       
   763 	{"port", SDT_UINT | SDT_NOSAVE, (void*)3979, &_network_port},
       
   764 	{"sync_freq", SDT_UINT | SDT_NOSAVE, (void*)4, &_network_sync_freq},
       
   765 	{"ahead_frames", SDT_UINT | SDT_NOSAVE, (void*)5, &_network_ahead_frames},
       
   766 	{NULL}
       
   767 };
       
   768 
       
   769 static const SettingDesc debug_settings[] = {
       
   770 	{"savedump_path", SDT_STRINGBUF | (lengthof(_savedump_path)<<16) | SDT_NOSAVE, NULL, _savedump_path},
       
   771 	{"savedump_first", SDT_UINT | SDT_NOSAVE, 0, &_savedump_first},
       
   772 	{"savedump_freq", SDT_UINT | SDT_NOSAVE, (void*)1, &_savedump_freq},
       
   773 	{"savedump_last", SDT_UINT | SDT_NOSAVE, 0, &_savedump_last},
       
   774 	{NULL}
       
   775 };
       
   776 
       
   777 
       
   778 static const SettingDesc gameopt_settings[] = {
       
   779 	{"diff_level", SDT_UINT8, (void*)9, (void*)offsetof(GameOptions, diff_level) },
       
   780 	{"diff_custom", SDT_INTLIST | SDT_UINT32 | (sizeof(GameDifficulty)/4) << 16, NULL, (void*)offsetof(GameOptions, diff) },
       
   781 	{"currency", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, currency), "GBP|USD|FF|DM|YEN|PT|FT|ZL|ATS|BEF|DKK|FIM|GRD|CHF|NLG|ITL|SEK|RUR|CZK|ISK|NOK|EUR" },
       
   782 	{"distances", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, kilometers), "imperial|metric" },
       
   783 	{"town_names", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, town_name), "english|french|german|american|latin|silly|swedish|dutch|finnish|polish|slovakish|hungarian" },
       
   784 	{"landscape", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, landscape), "normal|hilly|desert|candy" },
       
   785 	{"autosave", SDT_UINT8 | SDT_ONEOFMANY, (void*)1, (void*)offsetof(GameOptions, autosave), "off|monthly|quarterly|half year|yearly" },
       
   786 	{"road_side", SDT_UINT8 | SDT_ONEOFMANY, (void*)0, (void*)offsetof(GameOptions, road_side), "left|right" },
       
   787 
       
   788 	{NULL}
       
   789 };
       
   790 
       
   791 static const SettingDesc patch_settings[] = {
       
   792 	{"vehicle_speed", SDT_BOOL, (void*)true, (void*)offsetof(Patches, vehicle_speed) },
       
   793 	{"build_on_slopes", SDT_BOOL, (void*)true, (void*)offsetof(Patches, build_on_slopes) },
       
   794 	{"mammoth_trains", SDT_BOOL, (void*)true, (void*)offsetof(Patches, mammoth_trains) },
       
   795 	{"join_stations", SDT_BOOL, (void*)true, (void*)offsetof(Patches, join_stations) },
       
   796 	{"station_spread", SDT_UINT8, (void*)12, (void*)offsetof(Patches, station_spread) },
       
   797 	{"full_load_any", SDT_BOOL, (void*)true, (void*)offsetof(Patches, full_load_any)},
       
   798 
       
   799 	{"inflation", SDT_BOOL, (void*)true, (void*)offsetof(Patches, inflation)},
       
   800 	{"no_train_service", SDT_BOOL, (void*)false, (void*)offsetof(Patches, no_train_service)},
       
   801 	{"selectgoods", SDT_BOOL, (void*)true, (void*)offsetof(Patches, selectgoods)},
       
   802 	{"longbridges", SDT_BOOL, (void*)false, (void*)offsetof(Patches, longbridges)},
       
   803 	{"gotodepot", SDT_BOOL, (void*)true, (void*)offsetof(Patches, gotodepot)},
       
   804 
       
   805 	{"build_rawmaterial_ind", SDT_BOOL, (void*)false, (void*)offsetof(Patches, build_rawmaterial_ind)},
       
   806 	{"multiple_industry_per_town", SDT_BOOL, (void*)false, (void*)offsetof(Patches, multiple_industry_per_town)},
       
   807 	{"same_industry_close", SDT_BOOL, (void*)false, (void*)offsetof(Patches, same_industry_close)},
       
   808 
       
   809 	{"lost_train_days", SDT_UINT16, (void*)180, (void*)offsetof(Patches, lost_train_days)},
       
   810 	{"train_income_warn", SDT_BOOL, (void*)true, (void*)offsetof(Patches, train_income_warn)},
       
   811 
       
   812 	{"status_long_date", SDT_BOOL, (void*)true, (void*)offsetof(Patches, status_long_date)},
       
   813 	{"signal_side", SDT_BOOL, (void*)true, (void*)offsetof(Patches, signal_side)},
       
   814 	{"show_finances", SDT_BOOL, (void*)true, (void*)offsetof(Patches, show_finances)},
       
   815 
       
   816 	{"new_nonstop", SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_nonstop)},
       
   817 	{"roadveh_queue", SDT_BOOL, (void*)false, (void*)offsetof(Patches, roadveh_queue)},
       
   818 
       
   819 	{"autoscroll", SDT_BOOL, (void*)false, (void*)offsetof(Patches, autoscroll)},
       
   820 	{"errmsg_duration", SDT_UINT8, (void*)5, (void*)offsetof(Patches, errmsg_duration)},
       
   821 	{"snow_line_height", SDT_UINT8, (void*)7, (void*)offsetof(Patches, snow_line_height)},
       
   822 
       
   823 	{"bribe", SDT_BOOL, (void*)false, (void*)offsetof(Patches, bribe)},
       
   824 	{"new_depot_finding", SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_depot_finding)},
       
   825 
       
   826 	{"nonuniform_stations", SDT_BOOL, (void*)false, (void*)offsetof(Patches, nonuniform_stations)},
       
   827 	{"always_small_airport", SDT_BOOL, (void*)false, (void*)offsetof(Patches, always_small_airport)},
       
   828 	{"realistic_acceleration", SDT_BOOL, (void*)false, (void*)offsetof(Patches, realistic_acceleration)},
       
   829 
       
   830 	{"max_trains", SDT_UINT8, (void*)80,(void*)offsetof(Patches, max_trains)},
       
   831 	{"max_roadveh", SDT_UINT8, (void*)80,(void*)offsetof(Patches, max_roadveh)},
       
   832 	{"max_aircraft", SDT_UINT8, (void*)40,(void*)offsetof(Patches, max_aircraft)},
       
   833 	{"max_ships", SDT_UINT8, (void*)50,(void*)offsetof(Patches, max_ships)},
       
   834 
       
   835 	{"servint_trains", SDT_UINT16, (void*)150,(void*)offsetof(Patches, servint_trains)},
       
   836 	{"servint_roadveh", SDT_UINT16, (void*)150,(void*)offsetof(Patches, servint_roadveh)},
       
   837 	{"servint_ships", SDT_UINT16, (void*)360,(void*)offsetof(Patches, servint_ships)},
       
   838 	{"servint_aircraft", SDT_UINT16, (void*)100,(void*)offsetof(Patches, servint_aircraft)},
       
   839 
       
   840 	{"autorenew", SDT_BOOL, (void*)false,(void*)offsetof(Patches, autorenew)},
       
   841 	{"autorenew_months", SDT_UINT16, (void*)-12, (void*)offsetof(Patches, autorenew_months)},
       
   842 
       
   843 	{"new_pathfinding",  SDT_BOOL, (void*)false, (void*)offsetof(Patches, new_pathfinding)},
       
   844 	{"pf_maxlength", SDT_UINT16, (void*)512, (void*)offsetof(Patches, pf_maxlength)},
       
   845 	{"pf_maxdepth", SDT_UINT8, (void*)16, (void*)offsetof(Patches, pf_maxdepth)},
       
   846 
       
   847 	{"build_in_pause",  SDT_BOOL, (void*)false, (void*)offsetof(Patches, build_in_pause)},
       
   848 
       
   849 	{"ai_disable_veh", SDT_UINT8, (void*)0, (void*)offsetof(Patches, ai_disable_veh)},
       
   850 	{"starting_date", SDT_UINT32, (void*)1950, (void*)offsetof(Patches, starting_date)},
       
   851 
       
   852 	{"colored_news_date", SDT_UINT32, (void*)2000, (void*)offsetof(Patches, colored_news_date)},
       
   853 
       
   854 	{"bridge_pillars",  SDT_BOOL, (void*)true, (void*)offsetof(Patches, bridge_pillars)},
       
   855 
       
   856 	{"keep_all_autosave",  SDT_BOOL, (void*)false, (void*)offsetof(Patches, keep_all_autosave)},
       
   857 
       
   858 	{"extra_dynamite",  SDT_BOOL, (void*)false, (void*)offsetof(Patches, extra_dynamite)},
       
   859 
       
   860 	{"never_expire_vehicles",  SDT_BOOL, (void*)false, (void*)offsetof(Patches, never_expire_vehicles)},
       
   861 	{"extend_vehicle_life", SDT_UINT8, (void*)0, (void*)offsetof(Patches, extend_vehicle_life)},
       
   862 
       
   863 	{"auto_euro",  SDT_BOOL, (void*)true, (void*)offsetof(Patches, auto_euro)},
       
   864 
       
   865 	{"serviceathelipad",  SDT_BOOL, (void*)true, (void*)offsetof(Patches, serviceathelipad)},
       
   866 	{"smooth_economy", SDT_BOOL, (void*)false, (void*)offsetof(Patches, smooth_economy)},
       
   867 	{"dist_local_authority", SDT_UINT8, (void*)20, (void*)offsetof(Patches, dist_local_authority)},
       
   868 
       
   869 	{"wait_oneway_signal", SDT_UINT8, (void*)15, (void*)offsetof(Patches, wait_oneway_signal)},
       
   870 	{"wait_twoway_signal", SDT_UINT8, (void*)41, (void*)offsetof(Patches, wait_twoway_signal)},
       
   871 
       
   872 	{NULL}
       
   873 };
       
   874 
       
   875 typedef void SettingDescProc(IniFile *ini, const SettingDesc *desc, void *grpname, void *base);
       
   876 
       
   877 static void HandleSettingDescs(IniFile *ini, SettingDescProc *proc)
       
   878 {
       
   879 	proc(ini, misc_settings, "misc", NULL);
       
   880 	proc(ini, win32_settings, "win32", NULL);
       
   881 	proc(ini, network_settings, "network", NULL);
       
   882 	proc(ini, music_settings, "music", &msf);
       
   883 	proc(ini, gameopt_settings, "gameopt", &_new_opt);
       
   884 	proc(ini, patch_settings, "patches", &_patches);
       
   885 
       
   886 	proc(ini, debug_settings, "debug", NULL);
       
   887 }
       
   888 
       
   889 void LoadGrfSettings(IniFile *ini)
       
   890 {
       
   891 	IniGroup *group = ini_getgroup(ini, "newgrf", -1);
       
   892 	IniItem *item;
       
   893 	int i;
       
   894 
       
   895 	if (!group)
       
   896 		return;
       
   897 
       
   898 	for(i=0; i!=lengthof(_newgrf_files); i++) {
       
   899 		char buf[3];
       
   900 		sprintf(buf, "%d", i);
       
   901 		item = ini_getitem(group, buf, false);
       
   902 		if (!item)
       
   903 			break;
       
   904 		_newgrf_files[i] = strdup(item->value);
       
   905 	}
       
   906 }
       
   907 
       
   908 void LoadFromConfig()
       
   909 {
       
   910 	IniFile *ini = ini_load(_config_file);
       
   911 	HandleSettingDescs(ini, load_setting_desc);
       
   912 	LoadGrfSettings(ini);
       
   913 	ini_free(ini);
       
   914 }
       
   915 
       
   916 void SaveToConfig()
       
   917 {
       
   918 	IniFile *ini = ini_load(_config_file);
       
   919 	HandleSettingDescs(ini, save_setting_desc);
       
   920 	ini_save(_config_file, ini);
       
   921 	ini_free(ini);
       
   922 }