frontlib: Rewrote the ini helper code, finished ini reading/writing support for all relevant file types
authorMedo <smaxein@googlemail.com>
Tue, 12 Jun 2012 21:10:11 +0200
changeset 7227 1c859f572d72
parent 7224 5143861c83bd
child 7230 240620f46dd7
frontlib: Rewrote the ini helper code, finished ini reading/writing support for all relevant file types
project_files/frontlib/ipc/ipcconn.c
project_files/frontlib/model/cfg.c
project_files/frontlib/model/team.c
project_files/frontlib/model/team.h
project_files/frontlib/model/weapon.c
project_files/frontlib/test.c
project_files/frontlib/util/inihelper.c
project_files/frontlib/util/inihelper.h
project_files/frontlib/util/util.c
project_files/frontlib/util/util.h
--- a/project_files/frontlib/ipc/ipcconn.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/ipc/ipcconn.c	Tue Jun 12 21:10:11 2012 +0200
@@ -15,7 +15,7 @@
  *
  * We don't need to worry about wasting a few kb though, and I like powers of two...
  */
-typedef struct _flib_ipcconn {
+struct _flib_ipcconn {
 	uint8_t readBuffer[8192];
 	int readBufferSize;
 
@@ -23,10 +23,10 @@
 	uint16_t port;
 
 	flib_tcpsocket *sock;
-} _flib_ipcconn;
+};
 
 flib_ipcconn *flib_ipcconn_create() {
-	flib_ipcconn *result = flib_malloc(sizeof(_flib_ipcconn));
+	flib_ipcconn *result = flib_malloc(sizeof(flib_ipcconn));
 	flib_acceptor *acceptor = flib_acceptor_create(0);
 
 	if(!result || !acceptor) {
--- a/project_files/frontlib/model/cfg.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/model/cfg.c	Tue Jun 12 21:10:11 2012 +0200
@@ -1,17 +1,16 @@
 #include "cfg.h"
 
-#include "../iniparser/iniparser.h"
-#include "../iniparser/dictionary.h"
 #include "../util/inihelper.h"
 #include "../util/logging.h"
 #include "../util/util.h"
 
 #include <stdio.h>
+#include <stdlib.h>
 
-static flib_cfg_meta *flib_cfg_meta_from_ini_handleError(flib_cfg_meta *result, dictionary *settingfile, dictionary *modfile) {
+static flib_cfg_meta *flib_cfg_meta_from_ini_handleError(flib_cfg_meta *result, flib_ini *settingfile, flib_ini *modfile) {
 	flib_cfg_meta_destroy(result);
-	iniparser_freedict(settingfile);
-	iniparser_freedict(modfile);
+	flib_ini_destroy(settingfile);
+	flib_ini_destroy(modfile);
 	return NULL;
 }
 
@@ -21,15 +20,15 @@
 		return NULL;
 	}
 	flib_cfg_meta *result = flib_calloc(1, sizeof(flib_cfg_meta));
-	dictionary *settingfile = iniparser_load(settingpath);
-	dictionary *modfile = iniparser_load(modpath);
+	flib_ini *settingfile = flib_ini_load(settingpath);
+	flib_ini *modfile = flib_ini_load(modpath);
 
 	if(!result || !settingfile || !modfile) {
 		return flib_cfg_meta_from_ini_handleError(result, settingfile, modfile);
 	}
 
-	result->settingCount = iniparser_getnsec(settingfile);
-	result->modCount = iniparser_getnsec(modfile);
+	result->settingCount = flib_ini_get_sectioncount(settingfile);
+	result->modCount = flib_ini_get_sectioncount(modfile);
 	result->settings = flib_calloc(result->settingCount, sizeof(flib_cfg_setting_meta));
 	result->mods = flib_calloc(result->modCount, sizeof(flib_cfg_mod_meta));
 
@@ -38,44 +37,45 @@
 	}
 
 	for(int i=0; i<result->settingCount; i++) {
-		char *sectionName = iniparser_getsecname(settingfile, i);
-		if(!sectionName) {
+		result->settings[i].iniName = flib_ini_get_sectionname(settingfile, i);
+		if(!result->settings[i].iniName) {
 			return flib_cfg_meta_from_ini_handleError(result, settingfile, modfile);
 		}
 
 		bool error = false;
-		result->settings[i].iniName = flib_strdupnull(sectionName);
-		result->settings[i].title = inihelper_getstringdup(settingfile, &error, sectionName, "title");
-		result->settings[i].engineCommand = inihelper_getstringdup(settingfile, &error, sectionName, "command");
-		result->settings[i].image = inihelper_getstringdup(settingfile, &error, sectionName, "image");
-		result->settings[i].checkOverMax = inihelper_getbool(settingfile, &error, sectionName, "checkOverMax");
-		result->settings[i].times1000 = inihelper_getbool(settingfile, &error, sectionName, "times1000");
-		result->settings[i].min = inihelper_getint(settingfile, &error, sectionName, "min");
-		result->settings[i].max = inihelper_getint(settingfile, &error, sectionName, "max");
-		result->settings[i].def = inihelper_getint(settingfile, &error, sectionName, "default");
+		error |= flib_ini_enter_section(settingfile, result->settings[i].iniName);
+		error |= flib_ini_get_str(settingfile, &result->settings[i].title, "title");
+		error |= flib_ini_get_str(settingfile, &result->settings[i].engineCommand, "command");
+		error |= flib_ini_get_str(settingfile, &result->settings[i].image, "image");
+		error |= flib_ini_get_bool(settingfile, &result->settings[i].checkOverMax, "checkOverMax");
+		error |= flib_ini_get_bool(settingfile, &result->settings[i].times1000, "times1000");
+		error |= flib_ini_get_int(settingfile, &result->settings[i].min, "min");
+		error |= flib_ini_get_int(settingfile, &result->settings[i].max, "max");
+		error |= flib_ini_get_int(settingfile, &result->settings[i].def, "default");
+
 		if(error) {
-			flib_log_e("Missing or malformed ini parameter in file %s, section %s", settingpath, sectionName);
+			flib_log_e("Missing or malformed ini parameter in file %s, section %s", settingpath, result->settings[i].iniName);
 			return flib_cfg_meta_from_ini_handleError(result, settingfile, modfile);
 		}
 	}
 
 	for(int i=0; i<result->modCount; i++) {
-		char *sectionName = iniparser_getsecname(modfile, i);
-		if(!sectionName) {
+		result->mods[i].iniName = flib_ini_get_sectionname(modfile, i);
+		if(!result->mods[i].iniName) {
 			return flib_cfg_meta_from_ini_handleError(result, settingfile, modfile);
 		}
 
 		bool error = false;
-		result->mods[i].iniName = flib_strdupnull(sectionName);
-		result->mods[i].bitmaskIndex = inihelper_getint(modfile, &error, sectionName, "bitmaskIndex");
+		error |= flib_ini_enter_section(modfile, result->mods[i].iniName);
+		error |= flib_ini_get_int(modfile, &result->mods[i].bitmaskIndex, "bitmaskIndex");
 		if(error) {
-			flib_log_e("Missing or malformed ini parameter in file %s, section %s", modpath, sectionName);
+			flib_log_e("Missing or malformed ini parameter in file %s, section %s", modpath, result->mods[i].iniName);
 			return flib_cfg_meta_from_ini_handleError(result, settingfile, modfile);
 		}
 	}
 
-	iniparser_freedict(settingfile);
-	iniparser_freedict(modfile);
+	flib_ini_destroy(settingfile);
+	flib_ini_destroy(modfile);
 	return result;
 }
 
@@ -124,8 +124,8 @@
 	return result;
 }
 
-flib_cfg *flib_cfg_from_ini_handleError(flib_cfg *result, dictionary *settingfile) {
-	iniparser_freedict(settingfile);
+flib_cfg *flib_cfg_from_ini_handleError(flib_cfg *result, flib_ini *settingfile) {
+	flib_ini_destroy(settingfile);
 	flib_cfg_destroy(result);
 	return NULL;
 }
@@ -135,36 +135,45 @@
 		flib_log_e("null parameter in flib_cfg_from_ini");
 		return NULL;
 	}
-	dictionary *settingfile = iniparser_load(filename);
+	flib_ini *settingfile = flib_ini_load(filename);
 	if(!settingfile) {
 		return NULL;
 	}
 
-	bool error = false;
-	char *schemename = inihelper_getstring(settingfile, &error, "Scheme", "name");
-	if(!schemename) {
+	char *schemename = NULL;
+	if(flib_ini_enter_section(settingfile, "Scheme")) {
+		flib_log_e("Missing section \"Scheme\" in config file %s.", filename);
+		return flib_cfg_from_ini_handleError(NULL, settingfile);
+	}
+	if(flib_ini_get_str(settingfile, &schemename, "name")) {
+		flib_log_e("Missing scheme name in config file %s.", filename);
 		return flib_cfg_from_ini_handleError(NULL, settingfile);
 	}
 
 	flib_cfg *result = flib_cfg_create(meta, schemename);
 
-	for(int i=0; i<meta->settingCount; i++) {
-		char *key = inihelper_createDictKey("BasicSettings", meta->settings[i].iniName);
-		if(!key) {
-			return flib_cfg_from_ini_handleError(result, settingfile);
+	if(flib_ini_enter_section(settingfile, "BasicSettings")) {
+		flib_log_w("Missing section \"BasicSettings\" in config file %s, using defaults.", filename);
+	} else {
+		for(int i=0; i<meta->settingCount; i++) {
+			if(flib_ini_get_int_opt(settingfile, &result->settings[i], meta->settings[i].iniName, meta->settings[i].def)) {
+				flib_log_e("Error reading BasicSetting %s in config file %s.", meta->settings[i].iniName, filename);
+				return flib_cfg_from_ini_handleError(result, settingfile);
+			}
 		}
-		result->settings[i] = iniparser_getint(settingfile, key, meta->settings[i].def);
-		free(key);
 	}
-	for(int i=0; i<meta->modCount; i++) {
-		char *key = inihelper_createDictKey("GameMods", meta->mods[i].iniName);
-		if(!key) {
-			return flib_cfg_from_ini_handleError(result, settingfile);
+
+	if(flib_ini_enter_section(settingfile, "GameMods")) {
+		flib_log_w("Missing section \"GameMods\" in config file %s, using defaults.", filename);
+	} else {
+		for(int i=0; i<meta->modCount; i++) {
+			if(flib_ini_get_bool_opt(settingfile, &result->mods[i], meta->mods[i].iniName, false)) {
+				flib_log_e("Error reading GameMod %s in config file %s.", meta->mods[i].iniName, filename);
+				return flib_cfg_from_ini_handleError(result, settingfile);
+			}
 		}
-		result->mods[i] = iniparser_getboolean(settingfile, key, false);
-		free(key);
 	}
-	iniparser_freedict(settingfile);
+	flib_ini_destroy(settingfile);
 	return result;
 }
 
@@ -173,35 +182,36 @@
 	if(!meta || !filename || !config || config->modCount!=meta->modCount || config->settingCount!=meta->settingCount) {
 		flib_log_e("Invalid parameter in flib_cfg_to_ini");
 	} else {
-		dictionary *dict = iniparser_load(filename);
-		if(!dict) {
-			dict = dictionary_new(0);
-		}
-		if(dict) {
+		flib_ini *ini = flib_ini_create(filename);
+		if(ini) {
 			bool error = false;
-			// Add the sections
-			error |= iniparser_set(dict, "Scheme", NULL);
-			error |= iniparser_set(dict, "BasicSettings", NULL);
-			error |= iniparser_set(dict, "GameMods", NULL);
 
 			// Add the values
-			error |= inihelper_setstr(dict, "Scheme", "name", config->schemeName);
-			for(int i=0; i<config->settingCount; i++) {
-				error |= inihelper_setint(dict, "BasicSettings", meta->settings[i].iniName, config->settings[i]);
-			}
-			for(int i=0; i<config->modCount; i++) {
-				error |= inihelper_setbool(dict, "GameMods", meta->mods[i].iniName, config->mods[i]);
+			error |= flib_ini_create_section(ini, "Scheme");
+			if(!error) {
+				error |= flib_ini_set_str(ini, "name", config->schemeName);
 			}
+
+
+			error |= flib_ini_create_section(ini, "BasicSettings");
 			if(!error) {
-				FILE *inifile = fopen(filename, "wb");
-				if(inifile) {
-					iniparser_dump_ini(dict, inifile);
-					fclose(inifile);
-					result = 0;
+				for(int i=0; i<config->settingCount; i++) {
+					error |= flib_ini_set_int(ini, meta->settings[i].iniName, config->settings[i]);
 				}
 			}
-			dictionary_del(dict);
+
+			error |= flib_ini_create_section(ini, "GameMods");
+			if(!error) {
+				for(int i=0; i<config->modCount; i++) {
+					error |= flib_ini_set_bool(ini, meta->mods[i].iniName, config->mods[i]);
+				}
+			}
+
+			if(!error) {
+				result = flib_ini_save(ini, filename);
+			}
 		}
+		flib_ini_destroy(ini);
 	}
 	return result;
 }
--- a/project_files/frontlib/model/team.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/model/team.c	Tue Jun 12 21:10:11 2012 +0200
@@ -3,84 +3,189 @@
 #include "../util/inihelper.h"
 #include "../util/util.h"
 #include "../util/logging.h"
+#include <string.h>
+#include <stdlib.h>
 
-static flib_team *from_ini_handleError(flib_team *result, dictionary *settingfile, char **bindingKeys) {
-	if(settingfile) {
-		iniparser_freedict(settingfile);
-	}
+static flib_team *from_ini_handleError(flib_team *result, flib_ini *settingfile) {
+	flib_ini_destroy(settingfile);
 	flib_team_destroy(result);
-	free(bindingKeys);
 	return NULL;
 }
 
 flib_team *flib_team_from_ini(const char *filename) {
 	flib_team *result = flib_calloc(1, sizeof(flib_team));
-	dictionary *settingfile = NULL;
-	char **bindingKeys = NULL;
+	flib_ini *ini = NULL;
 
 	if(!filename) {
 		flib_log_e("null parameter in flib_team_from_ini");
-		return from_ini_handleError(result, settingfile, bindingKeys);
+		return from_ini_handleError(result, ini);
 	}
 
 	if(!result) {
-		return from_ini_handleError(result, settingfile, bindingKeys);
+		return from_ini_handleError(result, ini);
+	}
+
+	ini = flib_ini_load(filename);
+	if(!ini) {
+		flib_log_e("Error loading team file %s", filename);
+		return from_ini_handleError(result, ini);
 	}
 
-	settingfile = iniparser_load(filename);
-	if(!settingfile) {
-		flib_log_e("Error loading team file %s", filename);
-		return from_ini_handleError(result, settingfile, bindingKeys);
+	if(flib_ini_enter_section(ini, "team")) {
+		flib_log_e("Missing section \"Team\" in team file %s", filename);
+		return from_ini_handleError(result, ini);
+	}
+	bool error = false;
+	error |= flib_ini_get_str(ini, &result->name, "name");
+	error |= flib_ini_get_str(ini, &result->grave, "grave");
+	error |= flib_ini_get_str(ini, &result->fort, "fort");
+	error |= flib_ini_get_str(ini, &result->voicepack, "voicepack");
+	error |= flib_ini_get_str(ini, &result->flag, "flag");
+	error |= flib_ini_get_int(ini, &result->rounds, "rounds");
+	error |= flib_ini_get_int(ini, &result->wins, "wins");
+	error |= flib_ini_get_int(ini, &result->campaignProgress, "campaignprogress");
+
+	int difficulty = 0;
+	error |= flib_ini_get_int(ini, &difficulty, "difficulty");
+
+	if(error) {
+		flib_log_e("Missing or malformed entry in section \"Team\" in file %s", filename);
+		return from_ini_handleError(result, ini);
 	}
 
-	bool error = false;
-	result->name = inihelper_getstringdup(settingfile, &error, "team", "name");
-	result->grave = inihelper_getstringdup(settingfile, &error, "team", "grave");
-	result->fort = inihelper_getstringdup(settingfile, &error, "team", "fort");
-	result->voicepack = inihelper_getstringdup(settingfile, &error, "team", "voicepack");
-	result->flag = inihelper_getstringdup(settingfile, &error, "team", "flag");
-	result->rounds = inihelper_getint(settingfile, &error, "team", "rounds");
-	result->wins = inihelper_getint(settingfile, &error, "team", "wins");
-	result->campaignProgress = inihelper_getint(settingfile, &error, "team", "campaignprogress");
-	int difficulty = inihelper_getint(settingfile, &error, "team", "difficulty");
-
-	char sectionName[10];
-	strcpy(sectionName, "hedgehog0");
 	for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
-		sectionName[8] = '0'+i;
-		result->hogs[i].name = inihelper_getstringdup(settingfile, &error, sectionName, "name");
-		result->hogs[i].hat = inihelper_getstringdup(settingfile, &error, sectionName, "hat");
-		result->hogs[i].rounds = inihelper_getint(settingfile, &error, sectionName, "rounds");
-		result->hogs[i].kills = inihelper_getint(settingfile, &error, sectionName, "kills");
-		result->hogs[i].deaths = inihelper_getint(settingfile, &error, sectionName, "deaths");
-		result->hogs[i].suicides = inihelper_getint(settingfile, &error, sectionName, "suicides");
+		char sectionName[32];
+		if(snprintf(sectionName, sizeof(sectionName), "hedgehog%i", i) <= 0) {
+			return from_ini_handleError(result, ini);
+		}
+		if(flib_ini_enter_section(ini, sectionName)) {
+			flib_log_e("Missing section \"%s\" in team file %s", sectionName, filename);
+			return from_ini_handleError(result, ini);
+		}
+		flib_hog *hog = &result->hogs[i];
+		error |= flib_ini_get_str(ini, &hog->name, "name");
+		error |= flib_ini_get_str(ini, &hog->hat, "hat");
+		error |= flib_ini_get_int(ini, &hog->rounds, "rounds");
+		error |= flib_ini_get_int(ini, &hog->kills, "kills");
+		error |= flib_ini_get_int(ini, &hog->deaths, "deaths");
+		error |= flib_ini_get_int(ini, &hog->suicides, "suicides");
 		result->hogs[i].difficulty = difficulty;
 		result->hogs[i].initialHealth = TEAM_DEFAULT_HEALTH;
+
+		if(error) {
+			flib_log_e("Missing or malformed entry in section \"%s\" in file %s", sectionName, filename);
+			return from_ini_handleError(result, ini);
+		}
 	}
 
-	result->bindingCount = iniparser_getsecnkeys(settingfile, "binds");
-	result->bindings = flib_calloc(result->bindingCount, sizeof(flib_binding));
-	bindingKeys = iniparser_getseckeys(settingfile, "binds");
-	if(!result->bindings || !bindingKeys) {
-		return from_ini_handleError(result, settingfile, bindingKeys);
-	}
-
-	for(int i=0; i<result->bindingCount; i++) {
-		result->bindings[i].binding = flib_strdupnull(iniparser_getstring(settingfile, bindingKeys[i], NULL));
-		// The key names all start with "binds:", so we skip that.
-		result->bindings[i].action = inihelper_urldecode(bindingKeys[i]+strlen("binds:"));
-		if(!result->bindings[i].action || !result->bindings[i].binding) {
-			error = true;
+	if(!flib_ini_enter_section(ini, "binds")) {
+		result->bindingCount = flib_ini_get_keycount(ini);
+		if(result->bindingCount<0) {
+			flib_log_e("Error reading bindings from file %s", filename);
+			result->bindingCount = 0;
+		}
+		result->bindings = flib_calloc(result->bindingCount, sizeof(flib_binding));
+		if(!result->bindings) {
+			return from_ini_handleError(result, ini);
+		}
+		for(int i=0; i<result->bindingCount; i++) {
+			char *keyname = flib_ini_get_keyname(ini, i);
+			if(!keyname) {
+				error = true;
+			} else {
+				result->bindings[i].action = flib_urldecode(keyname);
+				error |= !result->bindings[i].action;
+				error |= flib_ini_get_str(ini, &result->bindings[i].binding, keyname);
+			}
+			free(keyname);
 		}
 	}
 
 	if(error) {
 		flib_log_e("Error reading team file %s", filename);
-		return from_ini_handleError(result, settingfile, bindingKeys);
+		return from_ini_handleError(result, ini);
 	}
 
-	iniparser_freedict(settingfile);
-	free(bindingKeys);
+	flib_ini_destroy(ini);
+	return result;
+}
+
+static int writeTeamSection(const flib_team *team, flib_ini *ini) {
+	if(flib_ini_create_section(ini, "team")) {
+		return -1;
+	}
+	bool error = false;
+	error |= flib_ini_set_str(ini, "name",  team->name);
+	error |= flib_ini_set_str(ini, "grave", team->grave);
+	error |= flib_ini_set_str(ini, "fort", team->fort);
+	error |= flib_ini_set_str(ini, "voicepack", team->voicepack);
+	error |= flib_ini_set_str(ini, "flag", team->flag);
+	error |= flib_ini_set_int(ini, "rounds", team->rounds);
+	error |= flib_ini_set_int(ini, "wins", team->wins);
+	error |= flib_ini_set_int(ini, "campaignprogress", team->campaignProgress);
+	error |= flib_ini_set_int(ini, "difficulty", team->hogs[0].difficulty);
+	return error;
+}
+
+static int writeHogSections(const flib_team *team, flib_ini *ini) {
+	for(int i=0; i<HEDGEHOGS_PER_TEAM; i++) {
+		const flib_hog *hog = &team->hogs[i];
+		char sectionName[32];
+		if(snprintf(sectionName, sizeof(sectionName), "hedgehog%i", i) <= 0) {
+			return -1;
+		}
+		if(flib_ini_create_section(ini, sectionName)) {
+			return -1;
+		}
+		bool error = false;
+		error |= flib_ini_set_str(ini, "name", hog->name);
+		error |= flib_ini_set_str(ini, "hat", hog->hat);
+		error |= flib_ini_set_int(ini, "rounds", hog->rounds);
+		error |= flib_ini_set_int(ini, "kills", hog->kills);
+		error |= flib_ini_set_int(ini, "deaths", hog->deaths);
+		error |= flib_ini_set_int(ini, "suicides", hog->suicides);
+		if(error) {
+			return error;
+		}
+	}
+	return 0;
+}
+
+static int writeBindingSection(const flib_team *team, flib_ini *ini) {
+	if(flib_ini_create_section(ini, "binds")) {
+		return -1;
+	}
+	for(int i=0; i<team->bindingCount; i++) {
+		bool error = false;
+		char *action = flib_urlencode(team->bindings[i].action);
+		if(action) {
+			error |= flib_ini_set_str(ini, action, team->bindings[i].binding);
+			free(action);
+		} else {
+			error = true;
+		}
+		if(error) {
+			return error;
+		}
+	}
+	return 0;
+}
+
+int flib_team_to_ini(const char *filename, const flib_team *team) {
+	int result = -1;
+	if(!filename || !team) {
+		flib_log_e("null parameter in flib_team_to_ini");
+	} else {
+		flib_ini *ini = flib_ini_create(filename);
+		bool error = false;
+		error |= writeTeamSection(team, ini);
+		error |= writeHogSections(team, ini);
+		error |= writeBindingSection(team, ini);
+		if(!error) {
+			result = flib_ini_save(ini, filename);
+		}
+		flib_ini_destroy(ini);
+	}
 	return result;
 }
 
--- a/project_files/frontlib/model/team.h	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/model/team.h	Tue Jun 12 21:10:11 2012 +0200
@@ -79,6 +79,10 @@
  * that were already present. Note that not all fields of a team struct
  * are stored in the ini, some are only used intermittently to store
  * information about a team in the context of a game.
+ *
+ * The flib_team can handle "difficulty" on a per-hog basis, but it
+ * is only written per-team in the team file. The difficulty of the
+ * first hog is used for the entire team when writing.
  */
 int flib_team_to_ini(const char *filename, const flib_team *team);
 void flib_team_destroy(flib_team *team);
--- a/project_files/frontlib/model/weapon.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/model/weapon.c	Tue Jun 12 21:10:11 2012 +0200
@@ -1,6 +1,5 @@
 #include "weapon.h"
 
-#include "../iniparser/iniparser.h"
 #include "../util/inihelper.h"
 #include "../util/logging.h"
 #include "../util/util.h"
@@ -75,23 +74,30 @@
 	if(!filename) {
 		flib_log_e("null parameter in flib_weaponset_from_ini");
 	} else {
-		dictionary *settingfile = iniparser_load(filename);
-		if(!settingfile) {
+		flib_ini *ini = flib_ini_load(filename);
+		if(!ini) {
 			flib_log_e("Error loading weapon scheme file %s", filename);
-		} else {
+		} else if(!flib_ini_enter_section(ini, "weaponset")) {
 			bool error = false;
-			char *name = inihelper_getstring(settingfile, &error, "weaponset", "name");
-			char *loadout = inihelper_getstring(settingfile, &error, "weaponset", "loadout");
-			char *crateprob = inihelper_getstring(settingfile, &error, "weaponset", "crateprob");
-			char *crateammo = inihelper_getstring(settingfile, &error, "weaponset", "crateammo");
-			char *delay = inihelper_getstring(settingfile, &error, "weaponset", "delay");
+			char *name = NULL, *loadout = NULL, *crateprob = NULL, *crateammo = NULL, *delay = NULL;
+			error |= flib_ini_get_str(ini, &name, "name");
+			error |= flib_ini_get_str(ini, &loadout, "loadout");
+			error |= flib_ini_get_str(ini, &crateprob, "crateprob");
+			error |= flib_ini_get_str(ini, &crateammo, "crateammo");
+			error |= flib_ini_get_str(ini, &delay, "delay");
+
 			if(error) {
 				flib_log_e("Missing key in weapon scheme file %s", filename);
 			} else {
 				result = flib_weaponset_create_str(name, loadout, crateprob, crateammo, delay);
 			}
+			free(name);
+			free(loadout);
+			free(crateprob);
+			free(crateammo);
+			free(delay);
 		}
-		iniparser_freedict(settingfile);
+		flib_ini_destroy(ini);
 	}
 	return result;
 }
@@ -101,31 +107,19 @@
 	if(!filename || !set) {
 		flib_log_e("null parameter in flib_weaponset_to_ini");
 	} else {
-		dictionary *dict = iniparser_load(filename);
-		if(!dict) {
-			dict = dictionary_new(0);
-		}
-		if(dict) {
+		flib_ini *ini = flib_ini_create(filename);
+		if(!flib_ini_create_section(ini, "weaponset")) {
 			bool error = false;
-			// Add the sections
-			error |= iniparser_set(dict, "weaponset", NULL);
-
-			// Add the values
-			error |= inihelper_setstr(dict, "weaponset", "name", set->name);
-			error |= inihelper_setstr(dict, "weaponset", "loadout", set->loadout);
-			error |= inihelper_setstr(dict, "weaponset", "crateprob", set->crateprob);
-			error |= inihelper_setstr(dict, "weaponset", "crateammo", set->crateammo);
-			error |= inihelper_setstr(dict, "weaponset", "delay", set->delay);
+			error |= flib_ini_set_str(ini, "name", set->name);
+			error |= flib_ini_set_str(ini, "loadout", set->loadout);
+			error |= flib_ini_set_str(ini, "crateprob", set->crateprob);
+			error |= flib_ini_set_str(ini, "crateammo", set->crateammo);
+			error |= flib_ini_set_str(ini, "delay", set->delay);
 			if(!error) {
-				FILE *inifile = fopen(filename, "wb");
-				if(inifile) {
-					iniparser_dump_ini(dict, inifile);
-					fclose(inifile);
-					result = 0;
-				}
+				result = flib_ini_save(ini, filename);
 			}
-			dictionary_del(dict);
 		}
+		flib_ini_destroy(ini);
 	}
 	return result;
 }
--- a/project_files/frontlib/test.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/test.c	Tue Jun 12 21:10:11 2012 +0200
@@ -104,7 +104,7 @@
 	flib_gamesetup setup;
 	setup.gamescheme = flib_cfg_from_ini(metaconf, "scheme_shoppa.ini");
 	setup.map = flib_map_create_maze("Jungle", MAZE_SIZE_MEDIUM_TUNNELS);
-	setup.seed = "apsfooasdgnds";
+	setup.seed = "asparagus";
 	setup.script = NULL;
 	setup.teamcount = 2;
 	setup.teams = calloc(2, sizeof(flib_team*));
@@ -166,12 +166,37 @@
 	}
 }
 
+void testSave() {
+	FILE *demofile = fopen("testsave.42.hws", "rb");
+	assert(demofile);
+	flib_vector *vec = flib_vector_create();
+	uint8_t demobuf[512];
+	int len;
+	while((len=fread(demobuf, 1, 512, demofile))>0) {
+		flib_vector_append(vec, demobuf, len);
+	}
+	fclose(demofile);
+	flib_constbuffer constbuf = flib_vector_as_constbuffer(vec);
+	flib_gameconn *gameconn = flib_gameconn_create_loadgame("Medo42", constbuf.data, constbuf.size);
+	flib_vector_destroy(vec);
+	assert(gameconn);
+	flib_gameconn_onDisconnect(gameconn, &onDisconnect, &gameconn);
+	flib_gameconn_onGameRecorded(gameconn, &onGameRecorded, &gameconn);
+	startEngineGame(flib_gameconn_getport(gameconn));
+
+	while(gameconn) {
+		flib_gameconn_tick(gameconn);
+	}
+}
+
 int main(int argc, char *argv[]) {
 	flib_init(0);
 	flib_log_setLevel(FLIB_LOGLEVEL_ALL);
 
 	//testMapPreview();
-	testDemo();
+	//testDemo();
+	//testSave();
+	testGame();
 
 	flib_quit();
 	return 0;
--- a/project_files/frontlib/util/inihelper.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/util/inihelper.c	Tue Jun 12 21:10:11 2012 +0200
@@ -1,4 +1,7 @@
 #include "inihelper.h"
+#include "../iniparser/dictionary.h"
+#include "../iniparser/iniparser.h"
+
 #include "logging.h"
 #include "util.h"
 
@@ -9,167 +12,309 @@
 #include <errno.h>
 #include <stdarg.h>
 
-static bool keychar_needs_urlencoding(char c) {
-	return !isalnum(c);
-}
-
-char *inihelper_urlencode(const char *inbuf) {
-	if(!inbuf) {
-		return NULL;
-	}
-	size_t insize = strlen(inbuf);
-	if(insize > SIZE_MAX/4) {
-		return NULL;
-	}
-
-	char *outbuf = flib_malloc(insize*3+1);
-	if(!outbuf) {
-		return NULL;
-	}
+struct _flib_ini {
+	dictionary *inidict;
+	char *currentSection;
+};
 
-    size_t inpos = 0, outpos = 0;
-    while(inbuf[inpos]) {
-        if(!keychar_needs_urlencoding(inbuf[inpos])) {
-        	outbuf[outpos++] = inbuf[inpos++];
-        } else {
-            if(snprintf(outbuf+outpos, 4, "%%%02X", (unsigned)((uint8_t*)inbuf)[inpos])<0) {
-            	free(outbuf);
-            	return NULL;
-            }
-            inpos++;
-            outpos += 3;
-        }
-    }
-    outbuf[outpos] = 0;
-    char *shrunk = realloc(outbuf, outpos+1);
-    return shrunk ? shrunk : outbuf;
-}
-
-char *inihelper_urldecode(const char *inbuf) {
-	char *outbuf = flib_malloc(strlen(inbuf)+1);
-	if(!outbuf) {
-		return NULL;
-	}
-
-    size_t inpos = 0, outpos = 0;
-    while(inbuf[inpos]) {
-        if(inbuf[inpos] == '%' && isxdigit(inbuf[inpos+1]) && isxdigit(inbuf[inpos+2])) {
-            char temp[3] = {inbuf[inpos+1],inbuf[inpos+2],0};
-            outbuf[outpos++] = strtol(temp, NULL, 16);
-            inpos += 3;
-        } else {
-        	outbuf[outpos++] = inbuf[inpos++];
-        }
-    }
-    outbuf[outpos] = 0;
-    char *shrunk = realloc(outbuf, outpos+1);
-    return shrunk ? shrunk : outbuf;
-}
-
-char *inihelper_createDictKey(const char *sectionName, const char *keyName) {
-	if(!sectionName || !keyName) {
-		flib_log_e("null parameter in inihelper_createDictKey");
-		return NULL;
-	}
+static char *createDictKey(const char *sectionName, const char *keyName) {
 	return flib_asprintf("%s:%s", sectionName, keyName);
 }
 
-char *inihelper_getstring(dictionary *inifile, bool *error, const char *sectionName, const char *keyName) {
-	if(!inifile || !sectionName || !keyName) {
-		flib_log_e("null parameter in inihelper_getstring");
-		*error = true;
-		return NULL;
+/**
+ * Turns a string into a lowercase string, in-place.
+ */
+static void strToLower(char *str) {
+	if(str) {
+		while(*str) {
+			*str = tolower(*str);
+			str++;
+		}
 	}
-	char *extendedkey = inihelper_createDictKey(sectionName, keyName);
-	if(!extendedkey) {
-		*error = true;
-		return NULL;
+}
+
+flib_ini *flib_ini_create(const char *filename) {
+	flib_ini *result = NULL;
+	flib_ini *tmpIni = flib_calloc(1, sizeof(flib_ini));
+	if(tmpIni) {
+		if(filename) {
+			tmpIni->inidict = iniparser_load(filename);
+		}
+		if(!tmpIni->inidict) {
+			tmpIni->inidict = dictionary_new(0);
+		}
+		if(tmpIni->inidict) {
+			result = tmpIni;
+			tmpIni = NULL;
+		}
 	}
-	char *result = iniparser_getstring(inifile, extendedkey, NULL);
-	free(extendedkey);
-	if(!result) {
-		flib_log_d("Missing ini setting: %s/%s", sectionName, keyName);
-		*error = true;
+	flib_ini_destroy(tmpIni);
+	return result;
+}
+
+flib_ini *flib_ini_load(const char *filename) {
+	flib_ini *result = NULL;
+	if(!filename) {
+		flib_log_e("null parameter in flib_ini_load");
+	} else {
+		flib_ini *tmpIni = flib_calloc(1, sizeof(flib_ini));
+		if(tmpIni) {
+			tmpIni->inidict = iniparser_load(filename);
+			if(tmpIni->inidict) {
+				result = tmpIni;
+				tmpIni = NULL;
+			}
+		}
+		flib_ini_destroy(tmpIni);
 	}
 	return result;
 }
 
-char *inihelper_getstringdup(dictionary *inifile, bool *error, const char *sectionName, const char *keyName) {
-	return flib_strdupnull(inihelper_getstring(inifile, error, sectionName, keyName));
-}
-
-int inihelper_getint(dictionary *inifile, bool *error, const char *sectionName, const char *keyName) {
-	char *value = inihelper_getstring(inifile, error, sectionName, keyName);
-	if(!value) {
-		return 0;
+int flib_ini_save(flib_ini *ini, const char *filename) {
+	int result = INI_ERROR_OTHER;
+	if(!ini || !filename) {
+		flib_log_e("null parameter in flib_ini_save");
 	} else {
-		errno = 0;
-		long val = strtol(value, NULL, 10);
-		if(errno!=0) {
-			flib_log_w("Cannot parse ini setting %s/%s = \"%s\" as integer.", sectionName, keyName, value);
-			*error = true;
-			return 0;
-		}
-		if(val<INT_MIN || val>INT_MAX) {
-			flib_log_w("ini setting %s/%s = \"%s\" is too large or too small.", sectionName, keyName, value);
-			*error = true;
-			return 0;
+		FILE *file = fopen(filename, "wb");
+		if(!file) {
+			flib_log_e("Error opening file \"%s\" for writing.", filename);
+		} else {
+			iniparser_dump_ini(ini->inidict, file);
+			if(fclose(file)) {
+				flib_log_e("Write error on ini file \"%s\"", filename);
+			} else {
+				result = 0;
+			}
 		}
-		return (int)val;
-	}
-}
-
-bool inihelper_getbool(dictionary *inifile, bool *error, const char *sectionName, const char *keyName) {
-	char *value = inihelper_getstring(inifile, error, sectionName, keyName);
-	if(!value) {
-		return false;
-	} else {
-		bool trueval = strchr("1tTyY", value[0]);
-		bool falseval = strchr("0fFnN", value[0]);
-		if(!trueval && !falseval) {
-			flib_log_w("ini setting %s/%s = \"%s\" is not a recognized truth value.", sectionName, keyName, value);
-			*error = true;
-			return false;
-		} else {
-			return trueval;
-		}
-	}
-}
-
-int inihelper_setstr(dictionary *dict, const char *sectionName, const char *keyName, const char *value) {
-	int result = -1;
-	if(!dict || !sectionName || !keyName || !value) {
-		flib_log_e("null parameter in inihelper_setstr");
-	} else {
-		char *extendedkey = inihelper_createDictKey(sectionName, keyName);
-		if(extendedkey) {
-			result = iniparser_set(dict, extendedkey, value);
-		}
-		free(extendedkey);
 	}
 	return result;
 }
 
-int inihelper_setint(dictionary *dict, const char *sectionName, const char *keyName, int value) {
-	int result = -1;
-	if(!dict || !sectionName || !keyName) {
-		flib_log_e("null parameter in inihelper_setint");
+void flib_ini_destroy(flib_ini *ini) {
+	if(ini) {
+		if(ini->inidict) {
+			iniparser_freedict(ini->inidict);
+		}
+		free(ini->currentSection);
+		free(ini);
+	}
+}
+
+int flib_ini_enter_section(flib_ini *ini, const char *section) {
+	int result = INI_ERROR_OTHER;
+	if(ini) {
+		free(ini->currentSection);
+		ini->currentSection = NULL;
+	}
+	if(!ini || !section) {
+		flib_log_e("null parameter in flib_ini_enter_section");
 	} else {
-		char *strvalue = flib_asprintf("%i", value);
-		if(strvalue) {
-			result = inihelper_setstr(dict, sectionName, keyName, strvalue);
-			free(strvalue);
+		if(!iniparser_find_entry(ini->inidict, section)) {
+			result = INI_ERROR_NOTFOUND;
+		} else {
+			ini->currentSection = flib_strdupnull(section);
+			if(ini->currentSection) {
+				// Usually iniparser ignores case, but some section-handling functions don't,
+				// so we set it to lowercase manually
+				strToLower(ini->currentSection);
+				result = 0;
+			}
+		}
+	}
+	return result;
+}
+
+int flib_ini_create_section(flib_ini *ini, const char *section) {
+	int result = INI_ERROR_OTHER;
+	if(!ini || !section) {
+		flib_log_e("null parameter in flib_ini_create_section");
+	} else {
+		result = flib_ini_enter_section(ini, section);
+		if(result == INI_ERROR_NOTFOUND) {
+			if(iniparser_set(ini->inidict, section, NULL)) {
+				result = INI_ERROR_OTHER;
+			} else {
+				result = flib_ini_enter_section(ini, section);
+			}
 		}
 	}
 	return result;
 }
 
-int inihelper_setbool(dictionary *dict, const char *sectionName, const char *keyName, bool value) {
-	int result = -1;
-	if(!dict || !sectionName || !keyName) {
-		flib_log_e("null parameter in inihelper_setbool");
+/**
+ * The result is an internal string of the iniparser, don't free it.
+ */
+static char *findValue(dictionary *dict, const char *section, const char *key) {
+	char *result = NULL;
+	char *dictKey = createDictKey(section, key);
+	if(dictKey) {
+		result = iniparser_getstring(dict, dictKey, NULL);
+	}
+	free(dictKey);
+	return result;
+}
+
+int flib_ini_get_str(flib_ini *ini, char **outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str_opt(ini, &tmpValue, key, NULL);
+	if(result==0) {
+		if(tmpValue == NULL) {
+			result = INI_ERROR_NOTFOUND;
+		} else {
+			*outVar = tmpValue;
+			tmpValue = NULL;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_str_opt(flib_ini *ini, char **outVar, const char *key, const char *def) {
+	int result = INI_ERROR_OTHER;
+	if(!ini || !outVar || !key || !ini->currentSection) {
+		flib_log_e("null parameter or no current section in flib_ini_get_str_opt");
 	} else {
-		result = inihelper_setstr(dict, sectionName, keyName, value ? "true" : "false");
+		const char *value = findValue(ini->inidict, ini->currentSection, key);
+		if(!value) {
+			value = def;
+		}
+		char *valueDup = flib_strdupnull(value);
+		if(valueDup) {
+			*outVar = valueDup;
+			result = 0;
+		}
+	}
+	return result;
+}
+
+int flib_ini_get_int(flib_ini *ini, int *outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str(ini, &tmpValue, key);
+	if(result==0) {
+		errno = 0;
+		long val = strtol(tmpValue, NULL, 10);
+		if(errno!=0 || val<INT_MIN || val>INT_MAX) {
+			flib_log_w("Cannot parse ini setting %s/%s = \"%s\" as integer.", ini->currentSection, key, tmpValue);
+			result = INI_ERROR_FORMAT;
+		} else {
+			*outVar = val;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_int_opt(flib_ini *ini, int *outVar, const char *key, int def) {
+	int tmpValue;
+	int result = flib_ini_get_int(ini, &tmpValue, key);
+	if(result == 0) {
+		*outVar = tmpValue;
+	} else if(result == INI_ERROR_NOTFOUND) {
+		*outVar = def;
+		result = 0;
 	}
 	return result;
 }
+
+int flib_ini_get_bool(flib_ini *ini, bool *outVar, const char *key) {
+	char *tmpValue = NULL;
+	int result = flib_ini_get_str(ini, &tmpValue, key);
+	if(result==0) {
+		bool trueval = strchr("1tTyY", tmpValue[0]);
+		bool falseval = strchr("0fFnN", tmpValue[0]);
+		if(!trueval && !falseval) {
+			flib_log_w("ini setting %s/%s = \"%s\" is not a recognized truth value.", ini->currentSection, key, tmpValue);
+			result = INI_ERROR_FORMAT;
+		} else {
+			*outVar = trueval;
+		}
+	}
+	free(tmpValue);
+	return result;
+}
+
+int flib_ini_get_bool_opt(flib_ini *ini, bool *outVar, const char *key, bool def) {
+	bool tmpValue;
+	int result = flib_ini_get_bool(ini, &tmpValue, key);
+	if(result == 0) {
+		*outVar = tmpValue;
+	} else if(result == INI_ERROR_NOTFOUND) {
+		*outVar = def;
+		result = 0;
+	}
+	return result;
+}
+
+int flib_ini_set_str(flib_ini *ini, const char *key, const char *value) {
+	int result = INI_ERROR_OTHER;
+	if(!ini || !key || !value || !ini->currentSection) {
+		flib_log_e("null parameter or no current section in flib_ini_set_str");
+	} else {
+		char *dictKey = createDictKey(ini->currentSection, key);
+		if(dictKey) {
+			result = iniparser_set(ini->inidict, dictKey, value);
+		}
+		free(dictKey);
+	}
+	return result;
+}
+
+int flib_ini_set_int(flib_ini *ini, const char *key, int value) {
+	int result = INI_ERROR_OTHER;
+	char *strvalue = flib_asprintf("%i", value);
+	if(strvalue) {
+		result = flib_ini_set_str(ini, key, strvalue);
+	}
+	free(strvalue);
+	return result;
+}
+
+int flib_ini_set_bool(flib_ini *ini, const char *key, bool value) {
+	return flib_ini_set_str(ini, key, value ? "true" : "false");
+}
+
+int flib_ini_get_sectioncount(flib_ini *ini) {
+	int result = INI_ERROR_OTHER;
+	if(!ini) {
+		flib_log_e("null parameter in flib_ini_get_sectioncount");
+	} else {
+		result = iniparser_getnsec(ini->inidict);
+	}
+	return result;
+}
+
+char *flib_ini_get_sectionname(flib_ini *ini, int number) {
+	char *result = NULL;
+	if(!ini || number<0) {
+		flib_log_e("bad parameter in flib_ini_get_sectionname");
+	} else {
+		result = flib_strdupnull(iniparser_getsecname(ini->inidict, number));
+	}
+	return result;
+}
+
+int flib_ini_get_keycount(flib_ini *ini) {
+	int result = INI_ERROR_OTHER;
+	if(!ini || !ini->currentSection) {
+		flib_log_e("null parameter or no current section in flib_ini_get_keycount");
+	} else {
+		result = iniparser_getsecnkeys(ini->inidict, ini->currentSection);
+	}
+	return result;
+}
+
+char *flib_ini_get_keyname(flib_ini *ini, int number) {
+	char *result = NULL;
+	if(!ini || number<0 || !ini->currentSection) {
+		flib_log_e("bad parameter or no current section in flib_ini_get_keyname");
+	} else {
+		int keyCount = iniparser_getsecnkeys(ini->inidict, ini->currentSection);
+		char **keys = iniparser_getseckeys(ini->inidict, ini->currentSection);
+		if(keys && keyCount>number) {
+			// The keys are in the format section:key, so we have to skip the section and colon.
+			result = flib_strdupnull(keys[number]+strlen(ini->currentSection)+1);
+		}
+		free(keys);
+	}
+	return result;
+}
--- a/project_files/frontlib/util/inihelper.h	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/util/inihelper.h	Tue Jun 12 21:10:11 2012 +0200
@@ -9,65 +9,152 @@
 #ifndef INIHELPER_H_
 #define INIHELPER_H_
 
-#include "../iniparser/iniparser.h"
+#include <stdbool.h>
 
-#include <stdbool.h>
+#define INI_ERROR_NOTFOUND -1
+#define INI_ERROR_FORMAT -2
+#define INI_ERROR_OTHER -100
 
 struct _flib_ini;
-typedef struct _flib_inihelper flib_ini;
+typedef struct _flib_ini flib_ini;
 
 /**
- * Returned buffer must be free()d
+ * Create a new ini data structure, pre-filled with the contents of
+ * the file "filename" if it exists. If filename is null, or the file
+ * is not found, an empty ini will be created. However, if an error
+ * occurs while reading the ini file (or any other error), null
+ * is returned.
+ *
+ * This behavior is useful for modifying an existing ini file without
+ * discarding unknown keys.
  */
-char *inihelper_urlencode(const char *inbuf);
+flib_ini *flib_ini_create(const char *filename);
+
+/**
+ * Similar to flib_ini_create, but fails if the file is not found
+ * or if filename is null.
+ */
+flib_ini *flib_ini_load(const char *filename);
 
 /**
- * Returned buffer must be free()d
+ * Store the ini to the file "filename", overwriting
+ * the previous contents. Returns 0 on success.
  */
-char *inihelper_urldecode(const char *inbuf);
+int flib_ini_save(flib_ini *ini, const char *filename);
+
+void flib_ini_destroy(flib_ini *ini);
+
+/**
+ * Enter the section with the specified name. Returns 0 on
+ * success, INI_ERROR_NOTFOUND if the section does not exist
+ * and a different value if another error occurs.
+ * If an error occurs, there is no current section.
+ *
+ * The section name should only consist of letters and
+ * numbers.
+ */
+int flib_ini_enter_section(flib_ini *ini, const char *section);
 
 /**
- * Create a key in the format "sectionName:keyName"
- * Returned buffer must be free()d
+ * Creates and enters the section with the specified name. Simply
+ * enters the section if it exists already. Returns 0 on success
+ * and a different value if another error occurs.
+ * If an error occurs, there is no current section.
  */
-char *inihelper_createDictKey(const char *sectionName, const char *keyName);
+int flib_ini_create_section(flib_ini *ini, const char *section);
 
 /**
- * Returns an internal buffer, don't modify or free
- * Sets error to true if something goes wrong, leaves it unchanged otherwise.
+ * Find a key in the current section and store the value in outVar
+ * as a newly allocated string. Returns 0 on success, INI_ERROR_NOTFOUND
+ * if the key was not found and a different value for other errors,
+ * e.g. if there is no current section.
  */
-char *inihelper_getstring(dictionary *inifile, bool *error, const char *sectionName, const char *keyName);
+int flib_ini_get_str(flib_ini *ini, char **outVar, const char *key);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as a newly allocated string. If the key is not found, the default
+ * value will be used instead. Returns 0 on success.
+ */
+int flib_ini_get_str_opt(flib_ini *ini, char **outVar, const char *key, const char *def);
 
 /**
- * Returned buffer must be free()d
- * Sets error to true if something goes wrong, leaves it unchanged otherwise.
+ * Find a key in the current section and store the value in outVar
+ * as an int. Returns 0 on success, INI_ERROR_NOTFOUND
+ * if the key was not found, INI_ERROR_FORMAT if it was found but
+ * could not be converted to an int, and a different value for other
+ * errors, e.g. if there is no current section.
  */
-char *inihelper_getstringdup(dictionary *inifile, bool *error, const char *sectionName, const char *keyName);
+int flib_ini_get_int(flib_ini *ini, int *outVar, const char *key);
+
+/**
+ * Find a key in the current section and store the value in outVar
+ * as an int. If the key is not found, the default value will be used instead.
+ * Returns 0 on success, INI_ERROR_FORMAT if the value was found but
+ * could not be converted to int, and another value otherwise.
+ */
+int flib_ini_get_int_opt(flib_ini *ini, int *outVar, const char *key, int def);
 
 /**
- * Sets error to true if something goes wrong, leaves it unchanged otherwise.
+ * Find a key in the current section and store the value in outVar
+ * as a bool. Treats everything beginning with "Y", "T" or "1" as true,
+ * everything starting with "N", "F" or "1" as false.
+ *
+ * Returns 0 on success, INI_ERROR_NOTFOUND if the key was not found,
+ * INI_ERROR_FORMAT if the value could not be interpreted as boolean,
+ * and another value otherwise.
  */
-int inihelper_getint(dictionary *inifile, bool *error, const char *sectionName, const char *keyName);
+int flib_ini_get_bool(flib_ini *ini, bool *outVar, const char *key);
 
 /**
- * Sets error to true if something goes wrong, leaves it unchanged otherwise.
+ * Find a key in the current section and store the value in outVar
+ * as a bool. If the key is not found, the default value will be
+ * used instead. Returns 0 on success, INI_ERROR_FORMAT if the
+ * value could not be interpreted as boolean, and another value otherwise.
  */
-bool inihelper_getbool(dictionary *inifile, bool *error, const char *sectionName, const char *keyName);
+int flib_ini_get_bool_opt(flib_ini *ini, bool *outVar, const char *key, bool def);
 
 /**
- * Returns a nonzero value on error.
+ * In the current section, associate key with value. Returns 0 on success.
+ */
+int flib_ini_set_str(flib_ini *ini, const char *key, const char *value);
+
+/**
+ * In the current section, associate key with value. Returns 0 on success.
  */
-int inihelper_setstr(dictionary *dict, const char *sectionName, const char *keyName, const char *value);
+int flib_ini_set_int(flib_ini *ini, const char *key, int value);
+
+/**
+ * In the current section, associate key with value. Returns 0 on success.
+ */
+int flib_ini_set_bool(flib_ini *ini, const char *key, bool value);
+
+/**
+ * Returns the number of sections in the ini file, or a negative value on error.
+ */
+int flib_ini_get_sectioncount(flib_ini *ini);
 
 /**
- * Returns a nonzero value on error.
+ * Returns the name of the section, or NULL on error. The returned string must
+ * be free()d.
+ *
+ * Note: There is no guarantee that the order of the sections
+ * will remain stable if the ini is modified.
  */
-int inihelper_setint(dictionary *dict, const char *sectionName, const char *keyName, int value);
+char *flib_ini_get_sectionname(flib_ini *ini, int number);
 
 /**
- * Set an ini setting to "true" or "false".
- * Returns a nonzero value on error.
+ * Returns the number of keys in the current section, or -1 on error.
  */
-int inihelper_setbool(dictionary *dict, const char *sectionName, const char *keyName, bool value);
+int flib_ini_get_keycount(flib_ini *ini);
+
+/**
+ * Returns the name of the key in the current section, or NULL on error.
+ * The returned string must be free()d.
+ *
+ * Note: There is no guarantee that the order of the keys in a section
+ * will remain stable if the ini is modified.
+ */
+char *flib_ini_get_keyname(flib_ini *ini, int number);
 
 #endif /* INIHELPER_H_ */
--- a/project_files/frontlib/util/util.c	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/util/util.c	Tue Jun 12 21:10:11 2012 +0200
@@ -6,6 +6,7 @@
 #include <stdlib.h>
 #include <string.h>
 #include <stdio.h>
+#include <ctype.h>
 
 char *flib_asprintf(const char *fmt, ...) {
 	va_list argp;
@@ -68,3 +69,62 @@
 	}
 	return result;
 }
+
+static bool isAsciiAlnum(char c) {
+	return (c>='0' && c<='9') || (c>='a' && c <='z') || (c>='A' && c <='Z');
+}
+
+char *flib_urlencode(const char *inbuf) {
+	if(!inbuf) {
+		return NULL;
+	}
+	size_t insize = strlen(inbuf);
+	if(insize > SIZE_MAX/4) {
+		flib_log_e("String too long in flib_urlencode: %zu bytes.", insize);
+		return NULL;
+	}
+
+	char *outbuf = flib_malloc(insize*3+1);
+	if(!outbuf) {
+		return NULL;
+	}
+
+    size_t inpos = 0, outpos = 0;
+    while(inbuf[inpos]) {
+        if(isAsciiAlnum(inbuf[inpos])) {
+        	outbuf[outpos++] = inbuf[inpos++];
+        } else {
+            if(snprintf(outbuf+outpos, 4, "%%%02X", (unsigned)((uint8_t*)inbuf)[inpos])<0) {
+            	flib_log_e("printf error in flib_urlencode");
+            	free(outbuf);
+            	return NULL;
+            }
+            inpos++;
+            outpos += 3;
+        }
+    }
+    outbuf[outpos] = 0;
+    char *shrunk = realloc(outbuf, outpos+1);
+    return shrunk ? shrunk : outbuf;
+}
+
+char *flib_urldecode(const char *inbuf) {
+	char *outbuf = flib_malloc(strlen(inbuf)+1);
+	if(!outbuf) {
+		return NULL;
+	}
+
+    size_t inpos = 0, outpos = 0;
+    while(inbuf[inpos]) {
+        if(inbuf[inpos] == '%' && isxdigit(inbuf[inpos+1]) && isxdigit(inbuf[inpos+2])) {
+            char temp[3] = {inbuf[inpos+1],inbuf[inpos+2],0};
+            outbuf[outpos++] = strtol(temp, NULL, 16);
+            inpos += 3;
+        } else {
+        	outbuf[outpos++] = inbuf[inpos++];
+        }
+    }
+    outbuf[outpos] = 0;
+    char *shrunk = realloc(outbuf, outpos+1);
+    return shrunk ? shrunk : outbuf;
+}
--- a/project_files/frontlib/util/util.h	Tue Jun 12 11:25:05 2012 +0200
+++ b/project_files/frontlib/util/util.h	Tue Jun 12 21:10:11 2012 +0200
@@ -45,4 +45,22 @@
  */
 void *flib_calloc(size_t count, size_t elementsize);
 
+/**
+ * Replace all non-alphanumeric and non-ascii bytes with escape
+ * sequences in the form %XX. Does not modify the original string,
+ * but returns a newly allocated one that must be free()d. Returns
+ * null on failure or if null was passed as argument.
+ *
+ * This should work fine with all ASCII-based charsets including UTF-8.
+ */
+char *flib_urlencode(const char *str);
+
+/**
+ * Replace escape sequences of the form %XX with their byte values.
+ * Does not modify the original string, but returns a newly allocated
+ * one that must be free()d. Returns null on failure or if null was
+ * passed as argument.
+ */
+char *flib_urldecode(const char *str);
+
 #endif