Index: Daodan/src/Daodan.h
===================================================================
--- Daodan/src/Daodan.h	(revision 1008)
+++ Daodan/src/Daodan.h	(revision 1017)
@@ -8,4 +8,6 @@
 #include "Oni/Oni.h"
 
+#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
+
 extern HMODULE DDrDLLModule;
 extern HMODULE DDrONiModule;
Index: Daodan/src/Daodan_Config.c
===================================================================
--- Daodan/src/Daodan_Config.c	(revision 1008)
+++ Daodan/src/Daodan_Config.c	(revision 1017)
@@ -3,4 +3,5 @@
 #include <time.h>
 
+#include "Daodan.h"
 #include "Daodan_Config.h"
 #include "Daodan_Patch.h"
@@ -11,6 +12,4 @@
 
 #include "Inifile_Reader.h"
-
-#define ARRAY_SIZE(arr) (sizeof(arr) / sizeof(arr[0]))
 
 static const char* iniName = "daodan.ini";
@@ -48,4 +47,9 @@
 	} },
 	{ "gameplay", "Gameplay", {
+		{ "bindablecheats",
+			"Allows cheats to be bound to keys. Requires 'customactions' and 'cheattable' to be true.",
+			C_BOOL,
+			{.intBoolVal = true},
+			{.intBoolVal = true} },
 		{ "characterawareness",
 			"Makes AI remember the player.",
@@ -65,4 +69,9 @@
 		{ "cooldowntimer",
 			"Disables weapon cooldown exploit.",
+			C_BOOL,
+			{.intBoolVal = true},
+			{.intBoolVal = true} },
+		{ "customactions",
+			"Allows more actions to be bound through Daodan.",
 			C_BOOL,
 			{.intBoolVal = true},
Index: Daodan/src/Oni/Oni.h
===================================================================
--- Daodan/src/Oni/Oni.h	(revision 1008)
+++ Daodan/src/Oni/Oni.h	(revision 1017)
@@ -34,4 +34,9 @@
 } UUtRect;
 
+typedef struct {
+	uint32_t input;
+	float analog;
+} LItDeviceInput;
+
 #include "Symbols_Var.h"
 #include "Symbols_Func.h"
Index: Daodan/src/Oni/Symbols_Func.h
===================================================================
--- Daodan/src/Oni/Symbols_Func.h	(revision 1008)
+++ Daodan/src/Oni/Symbols_Func.h	(revision 1017)
@@ -101,5 +101,5 @@
 
 // Print message to console ?
-DefFunc(int, COrMessage_Print, ONICALL, (char* Message, char* Key, void* noidea), 0x004304B0);
+DefFunc(int, COrMessage_Print, ONICALL, (const char* Message, const char* Key, uint32_t fadeTime), 0x004304B0);
 DefFunc(UUtBool, COrCommand_Execute, ONICALL, (char* command), 0x004317D0);
 
@@ -171,4 +171,11 @@
 DefFunc(void, LIrPlatform_Terminate, ONICALL, (void), 0x00403620);
 
+// Key_config callback for each mapping found
+DefFunc(uint16_t, LIrBinding_Add, ONICALL, (uint32_t key, const char* name), 0x00403c60);
+
+// Called when processing key input
+DefFunc(void, LIrActionBuffer_Add, ONICALL, (void* unknown, LItDeviceInput* input), 0x00403b30);
+
+
 #undef DefFunc
 
Index: Daodan/src/Patches/Cheater.c
===================================================================
--- Daodan/src/Patches/Cheater.c	(revision 1008)
+++ Daodan/src/Patches/Cheater.c	(revision 1017)
@@ -10,4 +10,6 @@
 #include "../Daodan_Config.h"
 #include "Cheater.h"
+#include "Input.h"
+#include "Utility.h"
 
 union MSVC_EVIL_FLOAT_HACK
@@ -293,3 +295,21 @@
 		++*((unsigned int*)((char*)Ebp + 0xf6));
 }
- 
+
+
+static void BindableCheatCallback (CustomActionCallbackArgument cheatnum) {
+	uint8_t res = DDrCheater (cheatnum);
+	if (res)
+		COrMessage_Print(DDr_CheatTable[cheatnum].message_on, 0, 240);
+	else
+		COrMessage_Print(DDr_CheatTable[cheatnum].message_off, 0, 240);
+}
+
+void InitBindableCheats() {
+	oniCheatCode* cur;
+	for (cur = DDr_CheatTable; cur->name != 0; cur++) {
+//		char* val = malloc(20);
+//		sprintf(val, "cheat_%s", cur->name);
+		Input_RegisterCustomAction (cur->name, EVENT_KEYPRESS, 0, BindableCheatCallback, cur->func);
+	}
+}
+
Index: Daodan/src/Patches/Cheater.h
===================================================================
--- Daodan/src/Patches/Cheater.h	(revision 1008)
+++ Daodan/src/Patches/Cheater.h	(revision 1017)
@@ -49,4 +49,5 @@
 void __stdcall FallingFrames(void* Ebp);
 void ONICALL DDrCheater_LevelLoad();
+void InitBindableCheats();
 
 #endif
Index: Daodan/src/Patches/Input.c
===================================================================
--- Daodan/src/Patches/Input.c	(revision 1017)
+++ Daodan/src/Patches/Input.c	(revision 1017)
@@ -0,0 +1,87 @@
+#include <stdlib.h>
+#include <stdarg.h>
+
+#include "../Daodan.h"
+#include "Input.h"
+#include "../Oni/Oni.h"
+#include "../Daodan_Patch.h"
+#include "Utility.h"
+
+#define EVENT_KEYPRESS_SECURETIME 3
+
+typedef struct {
+	uint32_t key;
+	const char* actionname;
+	ActionEventType_t eventType;
+	uint32_t keydownTimeoutTicks;
+	int64_t lastevent;
+	
+	CustomActionCallback_t callback;	
+	CustomActionCallbackArgument callbackArgument;
+} DD_CustomAction_t;
+
+static DD_CustomAction_t customActions[100];
+
+
+void Input_RegisterCustomAction (const char* actionname, ActionEventType_t eventType, uint32_t keydownTimeoutTicks, CustomActionCallback_t callback, CustomActionCallbackArgument callbackArgument) {
+	uint16_t i = 0;
+	DD_CustomAction_t* cur = customActions;
+	
+	while ( (i < ARRAY_SIZE(customActions)) && (cur->callback != 0)) {
+		cur++;
+		i++;
+	}
+	
+	if (i < ARRAY_SIZE(customActions)) {
+		cur->actionname = actionname;
+		cur->eventType = eventType;
+		cur->keydownTimeoutTicks = keydownTimeoutTicks;
+		cur->callback = callback;
+		cur->callbackArgument = callbackArgument;
+	} else {
+		STARTUPMESSAGE("Registering action %s failed, maximum actions reached", actionname);
+	}
+}
+
+
+_LIrBinding_Add Oni_LIrBinding_Add = (_LIrBinding_Add)0;
+uint16_t ONICALL DD_LIrBinding_Add(uint32_t key, const char* name) {
+	DD_CustomAction_t* cur;
+	for (cur = customActions; cur->callback != 0; cur++) {
+		if (!strcmp(name, cur->actionname)) {
+			cur->key = key;
+			return 0;
+		}
+	}
+
+	return Oni_LIrBinding_Add(key, name);
+}
+
+_LIrActionBuffer_Add Oni_LIrActionBuffer_Add = (_LIrActionBuffer_Add)0;
+void ONICALL DD_LIrActionBuffer_Add(void* unknown, LItDeviceInput* input) {
+	DD_CustomAction_t* cur;
+	for (cur = customActions; cur->callback != 0; cur++) {
+		if (cur->key == input->input) {
+			int64_t curTime = UUrMachineTime_Sixtieths();
+			if (cur->eventType == EVENT_KEYPRESS) {
+				if (cur->lastevent + EVENT_KEYPRESS_SECURETIME < curTime) {
+					cur->callback(cur->callbackArgument);
+				}
+				cur->lastevent = curTime;
+			} else if (cur->eventType == EVENT_KEYDOWN) {
+				if (cur->lastevent + cur->keydownTimeoutTicks < curTime) {
+					cur->callback(cur->callbackArgument);
+					cur->lastevent = curTime;
+				}
+			}
+			return;
+		}
+	}
+	Oni_LIrActionBuffer_Add(unknown, input);
+}
+
+void Input_PatchCode () {
+	Oni_LIrBinding_Add = DDrPatch_MakeDetour((void*)LIrBinding_Add, (void*)DD_LIrBinding_Add);
+	Oni_LIrActionBuffer_Add = DDrPatch_MakeDetour((void*)LIrActionBuffer_Add, (void*)DD_LIrActionBuffer_Add);
+}
+
Index: Daodan/src/Patches/Input.h
===================================================================
--- Daodan/src/Patches/Input.h	(revision 1017)
+++ Daodan/src/Patches/Input.h	(revision 1017)
@@ -0,0 +1,19 @@
+#ifndef INPUT_H
+#define INPUT_H
+
+#include "../Daodan.h"
+
+typedef uint32_t CustomActionCallbackArgument;
+
+typedef void (*CustomActionCallback_t) (CustomActionCallbackArgument argument);
+
+typedef enum {
+	EVENT_KEYPRESS,
+	EVENT_KEYDOWN,
+} ActionEventType_t;
+
+void Input_PatchCode ();
+
+void Input_RegisterCustomAction (const char* actionname, ActionEventType_t eventType, uint32_t keydownTimeoutTicks, CustomActionCallback_t callback, CustomActionCallbackArgument callbackArgument);
+
+#endif
Index: Daodan/src/Patches/Patches.c
===================================================================
--- Daodan/src/Patches/Patches.c	(revision 1008)
+++ Daodan/src/Patches/Patches.c	(revision 1017)
@@ -6,4 +6,5 @@
 #include "../Daodan_Config.h"
 #include "GL.h"
+#include "Input.h"
 #include "../Daodan_Patch.h"
 #include "Utility.h"
@@ -131,4 +132,6 @@
 	return Oni_COrTextArea_Resize(inTextArea, inBounds, inNumTextEntries);
 }
+
+
 
 #define IMcShade_Red (0xFFFF0000)
@@ -372,4 +375,9 @@
 		// At end of ONrUnlockLevel to init values on level loading
 		DDrPatch_MakeJump((void*)(OniExe + 0x0010f021), (void*)DDrCheater_LevelLoad);
+		
+		if (DDrConfig_GetOptOfType("gameplay.bindablecheats", C_BOOL)->value.intBoolVal)
+		{
+			InitBindableCheats();
+		}
 	}
 	
@@ -437,4 +445,10 @@
 	{
 		Oni_COrTextArea_Resize = DDrPatch_MakeDetour((void*)COrTextArea_Resize, (void*)DD_COrTextArea_Resize);
+	}
+	
+	// Allow custom actions to be bound through Daodan
+	if (DDrConfig_GetOptOfType("gameplay.customactions", C_BOOL)->value.intBoolVal)
+	{
+		Input_PatchCode ();
 	}
 	
Index: Daodan/src/_Version.h
===================================================================
--- Daodan/src/_Version.h	(revision 1008)
+++ Daodan/src/_Version.h	(revision 1017)
@@ -5,6 +5,6 @@
 #define STRINGIZE(s) STRINGIZE2(s)
 
-#define DAODAN_VERSION_MAJOR 3
-#define DAODAN_VERSION_MINOR 9
+#define DAODAN_VERSION_MAJOR 4
+#define DAODAN_VERSION_MINOR 0
 #define DAODAN_VERSION_STRING STRINGIZE(DAODAN_VERSION_MAJOR) "." STRINGIZE(DAODAN_VERSION_MINOR)
 
