Index: /Daodan/build.bat
===================================================================
--- /Daodan/build.bat	(revision 345)
+++ /Daodan/build.bat	(revision 346)
@@ -1,1 +1,1 @@
-gcc -O3 -s -Wall -shared -fomit-frame-pointer -o build\binkw32.dll src\Oni_Symbols.S src\Daodan.c src\Daodan_DLLStubs.c src\Daodan_Patch.c src\Daodan_Utility.c src\Daodan_Win32.c src\Daodan_Cheater.c src\Daodan_Persistence.c src\daodan_gl.c -lgdi32
+gcc -O3 -s -Wall -shared -fomit-frame-pointer -o build\binkw32.dll src\Oni_Symbols.S src\Daodan.c src\Daodan_DLLStubs.c src\Daodan_Patch.c src\Daodan_Utility.c src\Daodan_Win32.c src\Daodan_Cheater.c src\Daodan_Persistence.c src\daodan_gl.c src\inifile_reader.c -lgdi32
Index: /Daodan/src/Daodan.c
===================================================================
--- /Daodan/src/Daodan.c	(revision 345)
+++ /Daodan/src/Daodan.c	(revision 346)
@@ -1,2 +1,4 @@
+#include <string.h>
+
 #include "Daodan.h"
 #include "Daodan_Patch.h"
@@ -14,4 +16,6 @@
 #include "daodan_gl.h"
 
+#include "inifile.h"
+
 HMODULE DDrDLLModule;
 HMODULE DDrONiModule;
@@ -19,5 +23,5 @@
 bool DDrPatch_Init()
 {
-	DDrStartupMessage("daodan attached, patching engine");
+	DDrStartupMessage("patching engine");
 	
 	// Font texture cache doubled
@@ -96,6 +100,67 @@
 }
 
+enum {s_unknown, s_language} ini_section;
+
+bool DDrIniCallback(char* section, bool newsection, char* name, char* value)
+{
+	if (newsection)
+	{
+		if (!stricmp(section, "language"))
+			ini_section = s_language;
+		else
+		{
+			ini_section = s_unknown;
+			DDrStartupMessage("unrecognised ini section \"%s\"", section);
+		}
+	}
+	
+	switch (ini_section)
+	{
+		case s_language:
+			if (!stricmp(name, "savepoint"))
+			{
+				DDrPatch_StrDup(OniExe + 0x000fd730, value);
+				DDrPatch_StrDup(OniExe + 0x000fd738, value);
+			}
+			else if (!stricmp(name, "syndicatewarehouse"))
+			{
+				DDrPatch_StrDup(OniExe + 0x000fd71a, value);
+				DDrPatch_StrDup(OniExe + 0x0010ef75, value);
+			}
+			else if (!stricmp(name, "blam"))
+				DDrPatch_StrDup(OniExe + 0x0010fb73, value);
+			else
+				DDrStartupMessage("unrecognised language item \"%s\"", name);
+			break;
+		default:
+			break;
+	}
+	
+	return true;
+}
+
+void DDrConfig()
+{
+	if (GetFileAttributes("daodan.ini") == INVALID_FILE_ATTRIBUTES)
+	{
+		DDrStartupMessage("daodan.ini doesn't exist, creating");
+		FILE* fp = fopen("daodan.ini", "w");
+		if (fp)
+		{
+			fputs("[Options]\n", fp);
+			fclose(fp);
+		}
+	}
+	
+	DDrStartupMessage("parsing daodan.ini...");
+	if (!inifile_read("daodan.ini", DDrIniCallback))
+		DDrStartupMessage("error reading daodan.ini, check your syntax!");
+	DDrStartupMessage("finished parsing");
+}
+
 void __cdecl DDrMain(int argc, char* argv[])
 {
+	DDrStartupMessage("daodan attached!");
+	DDrConfig();
 	DDrPatch_Init();
 	
Index: /Daodan/src/Daodan_Patch.c
===================================================================
--- /Daodan/src/Daodan_Patch.c	(revision 345)
+++ /Daodan/src/Daodan_Patch.c	(revision 346)
@@ -1,4 +1,5 @@
 #include "Daodan_Patch.h"
 #include <windows.h>
+#include <string.h>
 
 bool DDrPatch_MakeJump(void* from, void* to)
@@ -87,2 +88,16 @@
 		return false;
 }
+
+bool DDrPatch_StrDup(int* dest, const char* value)
+{
+	DWORD oldp;
+	
+	if (VirtualProtect(dest, 4, PAGE_EXECUTE_READWRITE, &oldp))
+	{
+		*dest = (int)strdup(value);
+		VirtualProtect(dest, 4, oldp, &oldp);
+		return true;
+	}
+	else
+		return false;
+}
Index: /Daodan/src/Daodan_Patch.h
===================================================================
--- /Daodan/src/Daodan_Patch.h	(revision 345)
+++ /Daodan/src/Daodan_Patch.h	(revision 346)
@@ -15,4 +15,5 @@
 bool DDrPatch_Int32(int* dest, int value);
 bool DDrPatch_Int16(short* dest, short value);
+bool DDrPatch_StrDup(int* dest, const char* value);
 
 #endif
Index: /Daodan/src/inifile.h
===================================================================
--- /Daodan/src/inifile.h	(revision 346)
+++ /Daodan/src/inifile.h	(revision 346)
@@ -0,0 +1,13 @@
+#pragma once
+#ifndef INIFILE_H
+#define INIFILE_H
+
+#include <stdbool.h>
+
+enum {inifile_cantread = -20};
+
+typedef bool (*inifile_callback)(char* section, bool newsection, char* name, char* value);
+
+bool inifile_read(char* filename, inifile_callback callback);
+
+#endif
Index: /Daodan/src/inifile_reader.c
===================================================================
--- /Daodan/src/inifile_reader.c	(revision 346)
+++ /Daodan/src/inifile_reader.c	(revision 346)
@@ -0,0 +1,79 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdbool.h>
+#include <string.h>
+#include <ctype.h>
+
+#include "inifile.h"
+
+bool inifile_read(char* filename, inifile_callback callback)
+{
+	FILE* fp = fopen(filename, "r");
+	char* inisection = "";
+	char readbuf[4096] = "";
+	char* readptr;
+	bool success = true;
+	bool newsection = false;
+	
+	if (!fp)
+		return inifile_cantread;
+	
+	while ((readptr = fgets(readbuf, sizeof(readbuf), fp))) // Loop through each line.
+	{
+		while (isspace(readptr[0])) // Skip whitespace.
+			readptr++;
+		
+		if (readptr[0] == '\0' || readptr[0] == '#' || readptr[0] == '!') // Skip empty lines and comments.
+			continue;
+		else if (readptr[0] == '[' && readptr[1] != ']') // It's a section header.
+		{
+			int i;
+			for (i = 2; readptr[i]; i ++) // Look for the ]
+				if (readptr[i] == ']')
+					break;
+			
+			if (readptr[i]) // Replace with a null or crash with error.
+				readptr[i] = '\0';
+			else
+			{
+				success = false;
+				break;
+			}
+			
+			if (inisection[0])
+				free(inisection);
+			inisection = strdup(readptr + 1);
+			newsection = true;
+		}
+		else // It's a value.
+		{
+			int i;
+			int equals = 0;
+			for (i = 0; readptr[i]; i ++) // Find the =
+				if (readptr[i] == '=')
+					equals = i;
+			
+			if (readptr[i - 1] == '\n')
+				readptr[i - 1] = '\0'; // Remove the trailing newline.
+			
+			if (equals) // If there's no equals, crash with error.
+			{
+				readptr[equals] = '\0';
+				if (!callback(inisection, newsection, readptr, readptr + equals + 1)) // If the callback is false, exit.
+					break;
+				newsection = false;
+			}
+			else
+			{
+				success = false;
+				break;
+			}
+		}
+	}
+	
+	if (inisection[0])
+		free(inisection);
+	
+	fclose(fp);
+	return success;
+}
Index: /Daodan/src/inifile_test.c
===================================================================
--- /Daodan/src/inifile_test.c	(revision 346)
+++ /Daodan/src/inifile_test.c	(revision 346)
@@ -0,0 +1,18 @@
+#include <stdio.h>
+#include <stdbool.h>
+#include "inifile.h"
+
+bool ini_callback(char* section, bool newsection, char* name, char* value)
+{
+	if (newsection)
+		puts("New Section!");
+	printf("Section: %s Name: %s Value: %s\n", section, name, value);
+	return true;
+}
+
+int main()
+{
+	if (!inifile_read("testini.ini", ini_callback))
+		puts("Read error!");
+	return 0;
+}
Index: /Daodan/src/testini.ini
===================================================================
--- /Daodan/src/testini.ini	(revision 346)
+++ /Daodan/src/testini.ini	(revision 346)
@@ -0,0 +1,13 @@
+  # Test comment
+
+test1=hi
+
+[TestA]
+test2=lol hi
+test3=this is test2
+test4=
+   test5=sup
+
+!This is test B
+      [Test B]
+test6=test B is awesome
