From 5bb4e9992679c9e8767d4eb6de54da1892ec13c6 Mon Sep 17 00:00:00 2001
From: aap <aap@papnet.eu>
Date: Fri, 24 Apr 2020 13:27:02 +0200
Subject: [PATCH] implemented debugmenu

---
 premake5.lua                |    6 +-
 src/core/AnimViewer.cpp     |    2 +-
 src/core/Game.cpp           |    2 +-
 src/core/debugmenu_public.h |  154 -----
 src/core/main.cpp           |    5 +-
 src/core/re3.cpp            |   40 +-
 src/extras/arrow.inc        |   16 +
 src/extras/cursor.inc       |   16 +
 src/extras/debugmenu.cpp    | 1268 +++++++++++++++++++++++++++++++++++
 src/extras/debugmenu.h      |   80 +++
 src/extras/inttypes.h       |  216 ++++++
 src/fakerw/fake.cpp         |    4 +-
 src/rw/RwHelper.cpp         |   11 +-
 src/rw/RwHelper.h           |    1 +
 14 files changed, 1621 insertions(+), 200 deletions(-)
 delete mode 100644 src/core/debugmenu_public.h
 create mode 100644 src/extras/arrow.inc
 create mode 100644 src/extras/cursor.inc
 create mode 100644 src/extras/debugmenu.cpp
 create mode 100644 src/extras/debugmenu.h
 create mode 100644 src/extras/inttypes.h

diff --git a/premake5.lua b/premake5.lua
index 14aab9d8..baf26a42 100644
--- a/premake5.lua
+++ b/premake5.lua
@@ -22,6 +22,7 @@ workspace "re3"
 	files { "src/text/*.*" }
 	files { "src/vehicles/*.*" }
 	files { "src/weapons/*.*" }
+	files { "src/extras/*.*" }
 	files { "eax/*.*" }
 
 	includedirs { "src" }
@@ -42,6 +43,7 @@ workspace "re3"
 	includedirs { "src/text" }
 	includedirs { "src/vehicles" }
 	includedirs { "src/weapons" }
+	includedirs { "src/extras" }
 	includedirs { "eax" }
 
 	includedirs { "dxsdk/include" }
@@ -73,7 +75,7 @@ workspace "re3"
        "set filename=%%~ni",
        "set fileextension=%%~xi",
        "set target=!path!!filename!!fileextension!",
-       "if exist \"!target!\" copy /y \"!file!\" \"!target!\"",
+       "copy /y \"!file!\" \"!target!\"",
        ")" }
     
     function setpaths (gamepath, exepath, scriptspath)
@@ -106,7 +108,7 @@ project "re3"
 	staticruntime "off"
 	
 	filter "configurations:not *RW"
-		prebuildcommands { "cd \"../librw\" && premake5 " .. _ACTION .. " && msbuild \"build/librw.sln\" /property:Configuration=%{cfg.longname} /property:Platform=\"win-x86-d3d9\"" }
+	--	prebuildcommands { "cd \"../librw\" && premake5 " .. _ACTION .. " && msbuild \"build/librw.sln\" /property:Configuration=%{cfg.longname} /property:Platform=\"win-x86-d3d9\"" }
 		defines { "LIBRW", "RW_D3D9" }
 	
 	filter "configurations:*RW"
diff --git a/src/core/AnimViewer.cpp b/src/core/AnimViewer.cpp
index cf3ec5bf..36ea20b7 100644
--- a/src/core/AnimViewer.cpp
+++ b/src/core/AnimViewer.cpp
@@ -37,6 +37,7 @@
 #include "Shadows.h"
 #include "Radar.h"
 #include "Hud.h"
+#include "debugmenu.h"
 
 int CAnimViewer::animTxdSlot = 0;
 CEntity *CAnimViewer::pTarget = nil;
@@ -208,7 +209,6 @@ PlayAnimation(RpClump *clump, AssocGroupId animGroup, AnimationId anim)
 	animAssoc->SetRun();
 }
 
-extern void (*DebugMenuProcess)(void);
 void
 CAnimViewer::Update(void)
 {
diff --git a/src/core/Game.cpp b/src/core/Game.cpp
index bfe6eee3..f72d342f 100644
--- a/src/core/Game.cpp
+++ b/src/core/Game.cpp
@@ -84,6 +84,7 @@
 #include "World.h"
 #include "ZoneCull.h"
 #include "Zones.h"
+#include "debugmenu.h"
 
 
 
@@ -618,7 +619,6 @@ void CGame::InitialiseWhenRestarting(void)
 	DMAudio.ChangeMusicMode(MUSICMODE_GAME);
 }
 
-extern void (*DebugMenuProcess)(void);
 void CGame::Process(void) 
 {
 	CPad::UpdatePads();
diff --git a/src/core/debugmenu_public.h b/src/core/debugmenu_public.h
deleted file mode 100644
index 778e7afe..00000000
--- a/src/core/debugmenu_public.h
+++ /dev/null
@@ -1,154 +0,0 @@
-
-extern "C" {
-
-typedef void (*TriggerFunc)(void);
-
-struct DebugMenuEntry;
-
-typedef DebugMenuEntry *(*DebugMenuAddInt8_TYPE)(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddInt16_TYPE)(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddInt32_TYPE)(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddInt64_TYPE)(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddUInt8_TYPE)(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddUInt16_TYPE)(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddUInt32_TYPE)(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddUInt64_TYPE)(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings);
-typedef DebugMenuEntry *(*DebugMenuAddFloat32_TYPE)(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound);
-typedef DebugMenuEntry *(*DebugMenuAddFloat64_TYPE)(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound);
-typedef DebugMenuEntry *(*DebugMenuAddCmd_TYPE)(const char *path, const char *name, TriggerFunc triggerFunc);
-typedef void (*DebugMenuEntrySetWrap_TYPE)(DebugMenuEntry *e, bool wrap);
-typedef void (*DebugMenuEntrySetStrings_TYPE)(DebugMenuEntry *e, const char **strings);
-typedef void (*DebugMenuEntrySetAddress_TYPE)(DebugMenuEntry *e, void *addr);
-
-struct DebugMenuAPI
-{
-	bool isLoaded;
-	HMODULE module;
-	DebugMenuAddInt8_TYPE addint8;
-	DebugMenuAddInt16_TYPE addint16;
-	DebugMenuAddInt32_TYPE addint32;
-	DebugMenuAddInt64_TYPE addint64;
-	DebugMenuAddUInt8_TYPE adduint8;
-	DebugMenuAddUInt16_TYPE adduint16;
-	DebugMenuAddUInt32_TYPE adduint32;
-	DebugMenuAddUInt64_TYPE adduint64;
-	DebugMenuAddFloat32_TYPE addfloat32;
-	DebugMenuAddFloat64_TYPE addfloat64;
-	DebugMenuAddCmd_TYPE addcmd;
-	DebugMenuEntrySetWrap_TYPE setwrap;
-	DebugMenuEntrySetStrings_TYPE setstrings;
-	DebugMenuEntrySetAddress_TYPE setaddress;
-};
-extern DebugMenuAPI gDebugMenuAPI;
-
-inline DebugMenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
-{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
-inline DebugMenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
-{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
-inline DebugMenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc)
-{ return gDebugMenuAPI.addcmd(path, name, triggerFunc); }
-inline void DebugMenuEntrySetWrap(DebugMenuEntry *e, bool wrap)
-{ gDebugMenuAPI.setwrap(e, wrap); }
-inline void DebugMenuEntrySetStrings(DebugMenuEntry *e, const char **strings)
-{ gDebugMenuAPI.setstrings(e, strings); }
-inline void DebugMenuEntrySetAddress(DebugMenuEntry *e, void *addr)
-{ gDebugMenuAPI.setaddress(e, addr); }
-
-inline bool DebugMenuLoad(void)
-{
-	if(gDebugMenuAPI.isLoaded)
-		return true;
-	HMODULE mod = LoadLibraryA("debugmenu");
-	if(mod == nil){
-		char modulePath[MAX_PATH];
-		HMODULE dllModule;
-		GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, (LPCTSTR)&gDebugMenuAPI, &dllModule);
-		GetModuleFileNameA(dllModule, modulePath, MAX_PATH);
-		char *p = strchr(modulePath, '\\');
-		if(p) p[1] = '\0';
-		strcat(modulePath, "debugmenu");
-		mod = LoadLibraryA(modulePath);
-	}
-	if(mod == nil)
-		return false;
-	gDebugMenuAPI.addint8 = (DebugMenuAddInt8_TYPE)GetProcAddress(mod, "DebugMenuAddInt8");
-	gDebugMenuAPI.addint16 = (DebugMenuAddInt16_TYPE)GetProcAddress(mod, "DebugMenuAddInt16");
-	gDebugMenuAPI.addint32 = (DebugMenuAddInt32_TYPE)GetProcAddress(mod, "DebugMenuAddInt32");
-	gDebugMenuAPI.addint64 = (DebugMenuAddInt64_TYPE)GetProcAddress(mod, "DebugMenuAddInt64");
-	gDebugMenuAPI.adduint8 = (DebugMenuAddUInt8_TYPE)GetProcAddress(mod, "DebugMenuAddUInt8");
-	gDebugMenuAPI.adduint16 = (DebugMenuAddUInt16_TYPE)GetProcAddress(mod, "DebugMenuAddUInt16");
-	gDebugMenuAPI.adduint32 = (DebugMenuAddUInt32_TYPE)GetProcAddress(mod, "DebugMenuAddUInt32");
-	gDebugMenuAPI.adduint64 = (DebugMenuAddUInt64_TYPE)GetProcAddress(mod, "DebugMenuAddUInt64");
-	gDebugMenuAPI.addfloat32 = (DebugMenuAddFloat32_TYPE)GetProcAddress(mod, "DebugMenuAddFloat32");
-	gDebugMenuAPI.addfloat64 = (DebugMenuAddFloat64_TYPE)GetProcAddress(mod, "DebugMenuAddFloat64");
-	gDebugMenuAPI.addcmd = (DebugMenuAddCmd_TYPE)GetProcAddress(mod, "DebugMenuAddCmd");
-	gDebugMenuAPI.setwrap = (DebugMenuEntrySetWrap_TYPE)GetProcAddress(mod, "DebugMenuEntrySetWrap");
-	gDebugMenuAPI.setstrings = (DebugMenuEntrySetStrings_TYPE)GetProcAddress(mod, "DebugMenuEntrySetStrings");
-	gDebugMenuAPI.setaddress = (DebugMenuEntrySetAddress_TYPE)GetProcAddress(mod, "DebugMenuEntrySetAddress");
-	gDebugMenuAPI.isLoaded = true;
-	gDebugMenuAPI.module = mod;
-	return true;
-}
-
-}
-
-// Also overload them for simplicity
-
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
-{ return gDebugMenuAPI.addint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
-{ return gDebugMenuAPI.adduint64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
-{ return gDebugMenuAPI.addfloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
-inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
-{ return gDebugMenuAPI.addfloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
-
-inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc)
-{
-	static const char *boolstr[] = { "Off", "On" };
-	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
-	DebugMenuEntrySetWrap(e, true);
-	return e;
-}
-inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc)
-{
-	static const char *boolstr[] = { "Off", "On" };
-	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
-	DebugMenuEntrySetWrap(e, true);
-	return e;
-}
-inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc)
-{
-	static const char *boolstr[] = { "Off", "On" };
-	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
-	DebugMenuEntrySetWrap(e, true);
-	return e;
-}
diff --git a/src/core/main.cpp b/src/core/main.cpp
index 7013b10f..7c1f7938 100644
--- a/src/core/main.cpp
+++ b/src/core/main.cpp
@@ -59,6 +59,7 @@
 #include "timebars.h"
 #include "GenericGameStorage.h"
 #include "SceneEdit.h"
+#include "debugmenu.h"
 
 GlobalScene Scene;
 
@@ -90,9 +91,7 @@ void GameInit(void);
 void SystemInit(void);
 void TheGame(void);
 
-extern void (*DebugMenuProcess)(void);
-extern void (*DebugMenuRender)(void);
-void DebugMenuInit(void);
+void DebugMenuInit(void) {}
 void DebugMenuPopulate(void);
 
 
diff --git a/src/core/re3.cpp b/src/core/re3.cpp
index 4cec5773..d4d2c208 100644
--- a/src/core/re3.cpp
+++ b/src/core/re3.cpp
@@ -17,7 +17,6 @@
 #include "Heli.h"
 #include "Automobile.h"
 #include "Ped.h"
-#include "debugmenu_public.h"
 #include "Particle.h"
 #include "Console.h"
 #include "Debug.h"
@@ -26,6 +25,7 @@
 #include "Pad.h"
 #include "PlayerPed.h"
 #include "Radar.h"
+#include "debugmenu.h"
 
 #include <list>
 
@@ -33,8 +33,6 @@
 extern "C" int vsprintf(char* const _Buffer, char const* const _Format, va_list  _ArgList);
 #endif
 
-DebugMenuAPI gDebugMenuAPI;
-
 
 #ifdef USE_PS2_RAND
 unsigned __int64 myrand_seed = 1;
@@ -62,24 +60,6 @@ mysrand(unsigned int seed)
 	myrand_seed = seed;
 }
 
-void (*DebugMenuProcess)(void);
-void (*DebugMenuRender)(void);
-static void stub(void) { }
-
-void
-DebugMenuInit(void)
-{
-	if(DebugMenuLoad()){
-		DebugMenuProcess = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuProcess");
-		DebugMenuRender = (void(*)(void))GetProcAddress(gDebugMenuAPI.module, "DebugMenuRender");
-	}
-	if(DebugMenuProcess == nil || DebugMenuRender == nil){
-		DebugMenuProcess = stub;
-		DebugMenuRender = stub;
-	}
-
-}
-
 void WeaponCheat();
 void HealthCheat();
 void TankCheat();
@@ -269,7 +249,7 @@ TWEAKSWITCH(CWeather::NewWeatherType, 0, 3, wt, NULL);
 void
 DebugMenuPopulate(void)
 {
-	if(DebugMenuLoad()){
+	if(1){
 		static const char *weathers[] = {
 			"Sunny", "Cloudy", "Rainy", "Foggy"
 		};
@@ -284,7 +264,7 @@ DebugMenuPopulate(void)
 		e = DebugMenuAddVar("Time & Weather", "New Weather", (int16*)&CWeather::NewWeatherType, nil, 1, 0, 3, weathers);
 		DebugMenuEntrySetWrap(e, true);
 		DebugMenuAddVar("Time & Weather", "Wind", (float*)&CWeather::Wind, nil, 0.1f, 0.0f, 1.0f);
-		DebugMenuAddVar("Time & Weather", "Time scale", (float*)0x8F2C20, nil, 0.1f, 0.0f, 10.0f);
+		DebugMenuAddVar("Time & Weather", "Time scale", (float*)&CTimer::GetTimeScale(), nil, 0.1f, 0.0f, 10.0f);
 
 		DebugMenuAddCmd("Cheats", "Weapons", WeaponCheat);
 		DebugMenuAddCmd("Cheats", "Money", MoneyCheat);
@@ -357,7 +337,7 @@ DebugMenuPopulate(void)
 		DebugMenuAddCmd("Debug", "Catalina Fly By", CHeli::StartCatalinaFlyBy);
 		DebugMenuAddCmd("Debug", "Catalina Take Off", CHeli::CatalinaTakeOff);
 		DebugMenuAddCmd("Debug", "Catalina Fly Away", CHeli::MakeCatalinaHeliFlyAway);
-		DebugMenuAddVarBool8("Debug", "Script Heli On", (int8*)0x95CD43, nil);
+		DebugMenuAddVarBool8("Debug", "Script Heli On", (int8*)&CHeli::ScriptHeliOn, nil);
 
 		DebugMenuAddVarBool8("Debug", "Show Ped Paths", (int8*)&gbShowPedPaths, nil);
 		DebugMenuAddVarBool8("Debug", "Show Car Paths", (int8*)&gbShowCarPaths, nil);
@@ -397,18 +377,6 @@ DebugMenuPopulate(void)
 	}
 }
 
-/*
-int (*RsEventHandler_orig)(int a, int b);
-int
-delayedPatches10(int a, int b)
-{
-	DebugMenuInit();
-	DebugMenuPopulate();
-
-	return RsEventHandler_orig(a, b);
-}
-*/
-
 const int   re3_buffsize = 1024;
 static char re3_buff[re3_buffsize];
 
diff --git a/src/extras/arrow.inc b/src/extras/arrow.inc
new file mode 100644
index 00000000..8ea78283
--- /dev/null
+++ b/src/extras/arrow.inc
@@ -0,0 +1,16 @@
+255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  
+0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
+0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  0, 0, 0, 0,  
diff --git a/src/extras/cursor.inc b/src/extras/cursor.inc
new file mode 100644
index 00000000..e8afd394
--- /dev/null
+++ b/src/extras/cursor.inc
@@ -0,0 +1,16 @@
+255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  255, 255, 255, 255,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  255, 255, 255, 255,  0, 0, 0, 255,  0, 0, 0, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  255, 255, 255, 255,  0, 0, 0, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
+255, 255, 255, 255,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  255, 255, 255, 255,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  119, 119, 119, 0,  
diff --git a/src/extras/debugmenu.cpp b/src/extras/debugmenu.cpp
new file mode 100644
index 00000000..d6ee18ab
--- /dev/null
+++ b/src/extras/debugmenu.cpp
@@ -0,0 +1,1268 @@
+#include "common.h"
+#include "RwHelper.h"
+#include "Pad.h"
+#include "ControllerConfig.h"
+#include "Timer.h"
+#include "rtcharse.h"
+#include "inttypes.h"
+#include "debugmenu.h"
+
+#define snprintf _snprintf
+
+#define strdup _strdup
+
+
+
+// Font stuff
+struct Pt
+{
+	int x, y;
+};
+
+enum MenuFontStyle
+{
+	MENUFONT_NORMAL,
+	MENUFONT_SEL_ACTIVE,
+	MENUFONT_SEL_INACTIVE,
+	MENUFONT_MOUSE
+};
+
+RtCharset *fontStyles[4];
+RtCharsetDesc fontDesc;
+int fontscale = 1;	// not supported right now
+
+Pt
+fontGetStringSize(const char *s)
+{
+	Pt sz = { 0, 0 };
+	int x;
+	char c;
+	sz.y = fontDesc.height*fontscale;	// always assume one line;
+	x = 0;
+	while(c = *s++){
+		if(c == '\n'){
+			sz.y += fontDesc.height*fontscale;
+			if(x > sz.x)
+				sz.x = x;
+			x = 0;
+		}else
+			x += fontDesc.width*fontscale;
+	}
+	if(x > sz.x)
+		sz.x = x;
+	return sz;
+}
+
+Pt
+fontPrint(const char *s, float x, float y, int style)
+{
+	RtCharsetPrintBuffered(fontStyles[style], s, x, y, false);
+	return fontGetStringSize(s);
+}
+
+int
+fontGetLen(int len)
+{
+	return len*fontDesc.width*fontscale;
+}
+
+
+void
+createMenuFont(void)
+{
+	OpenCharsetSafe();
+
+	RwRGBA fg_normal = { 255, 255, 255, 255 };
+	RwRGBA bg_normal = { 255, 255, 255, 0 };
+	fontStyles[MENUFONT_NORMAL] = RtCharsetCreate(&fg_normal, &bg_normal);
+	assert(fontStyles[MENUFONT_NORMAL]);
+
+	RwRGBA fg_sel_active = { 200, 200, 200, 255 };
+	RwRGBA bg_sel_active = { 132, 132, 132, 255 };
+	fontStyles[MENUFONT_SEL_ACTIVE] = RtCharsetCreate(&fg_sel_active, &bg_sel_active);
+	assert(fontStyles[MENUFONT_SEL_ACTIVE]);
+
+	RwRGBA fg_sel_inactive = { 200, 200, 200, 255 };
+	RwRGBA bg_sel_inactive = { 200, 200, 200, 0 };
+	fontStyles[MENUFONT_SEL_INACTIVE] = RtCharsetCreate(&fg_sel_inactive, &bg_sel_inactive);
+	assert(fontStyles[MENUFONT_SEL_INACTIVE]);
+
+	RwRGBA fg_mouse = { 255, 255, 255, 255 };
+	RwRGBA bg_mouse = { 132, 132, 132, 255 };
+	fontStyles[MENUFONT_MOUSE] = RtCharsetCreate(&fg_mouse, &bg_mouse);
+	assert(fontStyles[MENUFONT_MOUSE]);
+
+	RtCharsetGetDesc(fontStyles[MENUFONT_NORMAL], &fontDesc);
+}
+
+
+
+
+
+
+
+enum EntryType
+{
+	MENUEMPTY = 0,
+	MENUSUB,
+	MENUVAR,
+
+	MENUVAR_INT,
+	MENUVAR_FLOAT,
+	MENUVAR_CMD,
+
+	MENUSCROLL	// dummy
+};
+
+struct Menu
+{
+	Menu *parent;
+	RwRect r;
+	MenuEntry *entries;
+	int numEntries;
+	int maxNameWidth, maxValWidth;
+
+	MenuEntry *findEntry(const char *entryname);
+	void insertEntrySorted(MenuEntry *entry);
+	void appendEntry(MenuEntry *entry);
+
+	bool isScrollingUp, isScrollingDown;
+	int scrollStart;
+	int numVisible;
+	RwRect scrollUpR, scrollDownR;
+	void scroll(int off);
+
+	int selection;
+	MenuEntry *selectedEntry;	// updated by update
+	void changeSelection(int newsel);
+	void changeSelection(MenuEntry *e);
+
+	void update(void);
+	void draw(void);
+	Menu(void){ memset(this, 0, sizeof(Menu)); }
+};
+extern Menu toplevel;
+
+struct MenuEntry_Sub : MenuEntry
+{
+	Menu *submenu;
+
+	MenuEntry_Sub(const char *name, Menu *menu);
+};
+
+struct MenuEntry_Var : MenuEntry
+{
+	int maxvallen;
+	int vartype;
+	bool wrapAround;
+
+	virtual void processInput(bool mouseOver, bool selected) = 0;
+	int getValWidth(void) { return maxvallen; }
+	virtual void getValStr(char *str, int len) = 0;
+	MenuEntry_Var(const char *name, int type);
+};
+
+struct MenuEntry_Int : MenuEntry_Var
+{
+	virtual void setStrings(const char **strings) = 0;
+	virtual int findStringLen(void) = 0;
+	MenuEntry_Int(const char *name);
+};
+
+#define INTTYPES \
+	X(Int8, int8, 4, "%4" PRId8) \
+	X(Int16, int16, 6, "%6" PRId16) \
+	X(Int32, int32, 11, "%11" PRId32) \
+	X(Int64, int64, 21, "%21" PRId64) \
+	X(UInt8, uint8, 4, "%4" PRIu8) \
+	X(UInt16, uint16, 6, "%6" PRIu16) \
+	X(UInt32, uint32, 11, "%11" PRIu32) \
+	X(UInt64, uint64, 21, "%21" PRIu64)
+#define FLOATTYPES \
+	X(Float32, float, 11, "%11.3f") \
+	X(Float64, double, 11, "%11.3lf")
+
+#define X(NAME, TYPE, MAXLEN, FMT) \
+struct MenuEntry_##NAME : MenuEntry_Int														       \
+{																		       \
+	TYPE *variable;																       \
+	TYPE lowerBound, upperBound;														       \
+	TYPE step;																       \
+	TriggerFunc triggerFunc;														       \
+	const char *fmt;															       \
+	const char **strings;															       \
+																		       \
+	void processInput(bool mouseOver, bool selected);														       \
+	void getValStr(char *str, int len);													       \
+																		       \
+	void setStrings(const char **strings);													       \
+	int findStringLen(void);														       \
+	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings);     \
+};
+INTTYPES
+#undef X
+
+#define X(NAME, TYPE, MAXLEN, FMT) \
+struct MenuEntry_##NAME : MenuEntry_Var												 \
+{																 \
+	TYPE *variable;														 \
+	TYPE lowerBound, upperBound;												 \
+	TYPE step;														 \
+	TriggerFunc triggerFunc;												 \
+	const char *fmt;													 \
+																 \
+	void processInput(bool mouseOver, bool selected);												 \
+	void getValStr(char *str, int len);											 \
+																 \
+	MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound);	 \
+};
+FLOATTYPES
+#undef X
+
+struct MenuEntry_Cmd : MenuEntry_Var
+{
+	TriggerFunc triggerFunc;
+
+	void processInput(bool mouseOver, bool selected);
+	void getValStr(char *str, int len);
+
+	MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc);
+};
+
+
+Menu *findMenu(const char *name);
+
+
+
+#define MUHKEYS \
+	X(leftjustdown, rsLEFT) \
+	X(rightjustdown, rsRIGHT) \
+	X(upjustdown, rsUP) \
+	X(downjustdown, rsDOWN) \
+	X(pgupjustdown, rsPGUP) \
+	X(pgdnjustdown, rsPGDN)
+
+#define MUHBUTTONS \
+	X(button1justdown, 1) \
+	X(button2justdown, 2) \
+	X(button3justdown, 3)
+
+#define REPEATDELAY 700
+#define REPEATINTERVAL 50
+#define X(var, keycode) static int var;
+MUHKEYS
+#undef X
+static int downtime;
+static int repeattime;
+static int lastkeydown;
+static int *keyptr;
+
+static int buttondown[3];
+static int lastbuttondown;
+static int *buttonptr;
+static int button1justdown, button2justdown, button3justdown;
+static float mouseX, mouseY;
+
+static int menuOn;
+static int menuInitialized;
+static int screenWidth, screenHeight;
+static RwRaster *cursor, *arrow;
+
+static int firstBorder = 10;
+static int topBorder = 40;	//10;
+static int leading = 4;
+static int gap = 10;
+static int minwidth = 100;
+
+void drawMouse(void);
+void drawArrow(RwRect r, int direction, int style);
+
+Menu toplevel;
+Menu *activeMenu = &toplevel;
+Menu *deepestMenu = &toplevel;
+Menu *mouseOverMenu;
+MenuEntry *mouseOverEntry;
+MenuEntry scrollUpEntry("SCROLLUP"), scrollDownEntry("SCROLLDOWN");	// dummies
+
+
+#define KEYJUSTDOWN(k) ControlsManager.GetIsKeyboardKeyJustDown((RsKeyCodes)k)
+#define KEYDOWN(k) ControlsManager.GetIsKeyboardKeyDown((RsKeyCodes)k)
+#define CTRLJUSTDOWN(key) \
+	       ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYJUSTDOWN((RsKeyCodes)key) || \
+	        (KEYJUSTDOWN(rsLCTRL) || KEYJUSTDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))
+#define CTRLDOWN(key) ((KEYDOWN(rsLCTRL) || KEYDOWN(rsRCTRL)) && KEYDOWN((RsKeyCodes)key))
+
+
+bool
+isMouseInRect(RwRect r)
+{
+	return (mouseX >= r.x && mouseX < r.x+r.w &&
+		mouseY >= r.y && mouseY < r.y+r.h);
+}
+
+/*
+ * MenuEntry
+ */
+
+MenuEntry::MenuEntry(const char *name)
+{
+	this->type = MENUEMPTY;
+	this->name = strdup(name);
+	this->next = nil;
+	this->menu = nil;
+}
+
+MenuEntry_Sub::MenuEntry_Sub(const char *name, Menu *menu)
+: MenuEntry(name)
+{
+	this->type = MENUSUB;
+	this->submenu = menu;
+}
+
+MenuEntry_Var::MenuEntry_Var(const char *name, int vartype)
+: MenuEntry(name)
+{
+	this->type = MENUVAR;
+	this->vartype = vartype;
+	this->maxvallen = 0;
+	this->wrapAround = false;
+}
+
+/*
+ * *****************************
+ * MenuEntry_Int
+ * *****************************
+ */
+
+MenuEntry_Int::MenuEntry_Int(const char *name)
+: MenuEntry_Var(name, MENUVAR_INT)
+{
+}
+
+#define X(NAME, TYPE, MAXLEN, FMT) \
+int								     \
+MenuEntry_##NAME::findStringLen(void){				     \
+	TYPE i;							     \
+	int len, maxlen = 0;					     \
+	for(i = this->lowerBound; i <= this->upperBound; i++){	     \
+		len = strlen(this->strings[i-this->lowerBound]);     \
+		if(len > maxlen)				     \
+			maxlen = len;				     \
+	}							     \
+	return maxlen;						     \
+}								     \
+void								     \
+MenuEntry_##NAME::processInput(bool mouseOver, bool selected)				     \
+{								     \
+	TYPE v, oldv;						     \
+	int overflow = 0;					     \
+	int underflow = 0;					     \
+								     \
+	v = *this->variable;					     \
+	oldv = v;						     \
+								     \
+	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
+		v -= this->step;				     \
+		if(v > oldv)					     \
+			underflow = 1;				     \
+	}							     \
+	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
+		v += this->step;				     \
+		if(v < oldv)					     \
+			overflow = 1;				     \
+	}							     \
+	if(this->wrapAround){					     \
+		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
+		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
+	}else{									     \
+		if(v > this->upperBound || overflow) v = this->upperBound;	     \
+		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
+	}							     \
+								     \
+	*this->variable = v;					     \
+	if(oldv != v && this->triggerFunc)			     \
+		this->triggerFunc();				     \
+}								     \
+void											       \
+MenuEntry_##NAME::getValStr(char *str, int len)						       \
+{											       \
+	static char tmp[20];								       \
+	if(this->strings){								       \
+		snprintf(tmp, 20, "%%%ds", this->maxvallen);				       \
+		if(*this->variable < this->lowerBound || *this->variable > this->upperBound){  \
+			snprintf(str, len, "ERROR");					       \
+			return;								       \
+		}									       \
+		snprintf(str, len, tmp, this->strings[*this->variable-this->lowerBound]);      \
+	}else										       \
+		snprintf(str, len, this->fmt, *this->variable);				       \
+}											       \
+void																			   \
+MenuEntry_##NAME::setStrings(const char **strings)													   \
+{																			   \
+	this->strings = strings;															   \
+	if(this->strings)																   \
+		this->maxvallen = findStringLen();													   \
+}																			   \
+MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)   \
+: MenuEntry_Int(name)																	   \
+{																			   \
+	this->variable = ptr;																   \
+	this->step = step;																   \
+	this->lowerBound = lowerBound;															   \
+	this->upperBound = upperBound;															   \
+	this->triggerFunc = triggerFunc;														   \
+	this->maxvallen = MAXLEN;															   \
+	this->fmt = FMT;																   \
+	this->setStrings(strings);																   \
+}
+INTTYPES
+#undef X
+
+/*
+ * *****************************
+ * MenuEntry_Float
+ * *****************************
+ */
+
+#define X(NAME, TYPE, MAXLEN, FMT) \
+MenuEntry_##NAME::MenuEntry_##NAME(const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	     \
+: MenuEntry_Var(name, MENUVAR_FLOAT)															     \
+{																	     \
+	this->variable = ptr;														     \
+	this->step = step;														     \
+	this->lowerBound = lowerBound;													     \
+	this->upperBound = upperBound;													     \
+	this->triggerFunc = triggerFunc;												     \
+	this->maxvallen = MAXLEN;													     \
+	this->fmt = FMT;														     \
+}																	     \
+void																	     \
+MenuEntry_##NAME::getValStr(char *str, int len)												     \
+{																	     \
+	snprintf(str, len, this->fmt, *this->variable);											     \
+}																	     \
+void																	     \
+MenuEntry_##NAME::processInput(bool mouseOver, bool selected)													     \
+{																	     \
+	float v, oldv;															     \
+	int overflow = 0;					     \
+	int underflow = 0;					     \
+																	     \
+	v = *this->variable;														     \
+	oldv = v;															     \
+																	     \
+	if((selected && leftjustdown) || (mouseOver && button3justdown)){					     \
+		v -= this->step;				     \
+		if(v > oldv)					     \
+			underflow = 1;				     \
+	}							     \
+	if((selected && rightjustdown) || (mouseOver && button1justdown)){					     \
+		v += this->step;				     \
+		if(v < oldv)					     \
+			overflow = 1;				     \
+	}							     \
+	if(this->wrapAround){					     \
+		if(v > this->upperBound || overflow) v = this->lowerBound;	     \
+		if(v < this->lowerBound || underflow) v = this->upperBound;	     \
+	}else{									     \
+		if(v > this->upperBound || overflow) v = this->upperBound;	     \
+		if(v < this->lowerBound || underflow) v = this->lowerBound;	     \
+	}							     \
+																	     \
+	*this->variable = v;														     \
+	if(oldv != v && this->triggerFunc)												     \
+		this->triggerFunc();													     \
+}
+
+FLOATTYPES
+#undef X
+
+/*
+ * *****************************
+ * MenuEntry_Cmd
+ * *****************************
+ */
+
+void
+MenuEntry_Cmd::processInput(bool mouseOver, bool selected)
+{
+	// Don't execute on button3
+	if(this->triggerFunc && (selected && (leftjustdown || rightjustdown) || (mouseOver && button1justdown)))
+		this->triggerFunc();
+}
+
+void
+MenuEntry_Cmd::getValStr(char *str, int len)
+{
+	strncpy(str, "<", len);
+}
+
+MenuEntry_Cmd::MenuEntry_Cmd(const char *name, TriggerFunc triggerFunc)
+: MenuEntry_Var(name, MENUVAR_CMD)
+{
+	this->maxvallen = 1;
+	this->triggerFunc = triggerFunc;
+}
+
+
+/*
+ * *****************************
+ * Menu
+ * *****************************
+ */
+
+void
+Menu::scroll(int off) {
+	if(isScrollingUp && off < 0)
+		scrollStart += off;
+	if(isScrollingDown && off > 0)
+		scrollStart += off;
+	if(scrollStart < 0) scrollStart = 0;
+	if(scrollStart > numEntries-numVisible) scrollStart = numEntries-numVisible;
+}
+
+void
+Menu::changeSelection(int newsel){
+	selection = newsel;
+	if(selection < 0) selection = 0;
+	if(selection >= numEntries) selection = numEntries-1;
+	if(selection < scrollStart) scrollStart = selection;
+	if(selection >= scrollStart+numVisible) scrollStart = selection-numVisible+1;
+}
+
+void
+Menu::changeSelection(MenuEntry *sel)
+{
+	MenuEntry *e;
+	int i = 0;
+	for(e = this->entries; e; e = e->next){
+		if(e == sel){
+			this->selection = i;
+			this->selectedEntry = sel;
+			break;
+		}
+		i++;
+	}
+}
+
+
+
+MenuEntry*
+Menu::findEntry(const char *entryname)
+{
+	MenuEntry *m;
+	for(m = this->entries; m; m = m->next)
+		if(strcmp(entryname, m->name) == 0)
+			return m;
+	return nil;
+}
+
+void
+Menu::insertEntrySorted(MenuEntry *entry)
+{
+	MenuEntry **mp;
+	int cmp;
+	for(mp = &this->entries; *mp; mp = &(*mp)->next){
+		cmp = strcmp(entry->name, (*mp)->name);
+		if(cmp == 0)
+			return;
+		if(cmp < 0)
+			break;
+	}
+	entry->next = *mp;
+	*mp = entry;
+	entry->menu = this;
+	this->numEntries++;
+}
+
+void
+Menu::appendEntry(MenuEntry *entry)
+{
+	MenuEntry **mp;
+	for(mp = &this->entries; *mp; mp = &(*mp)->next);
+	entry->next = *mp;
+	*mp = entry;
+	entry->menu = this;
+	this->numEntries++;
+}
+
+void
+Menu::update(void)
+{
+	int i;
+	int x, y;
+	Pt sz;
+	MenuEntry *e;
+	int onscreen;
+	x = this->r.x;
+	y = this->r.y + 18;
+	int end = this->r.y+this->r.h - 18;
+	this->numVisible = 0;
+
+	deepestMenu = this;
+
+	int bottomy = end;
+	onscreen = 1;
+	i = 0;
+	this->maxNameWidth = 0;
+	this->maxValWidth = 0;
+	this->isScrollingUp = this->scrollStart > 0;
+	this->isScrollingDown = false;
+	this->selectedEntry = nil;
+	for(e = this->entries; e; e = e->next){
+		sz = fontGetStringSize(e->name);
+		e->r.x = x;
+		e->r.y = y;
+		e->r.w = sz.x;
+		e->r.h = sz.y;
+
+		if(i == this->selection)
+			this->selectedEntry = e;
+
+		if(i >= this->scrollStart)
+			y += sz.y + leading*fontscale;
+		if(y >= end){
+			this->isScrollingDown = true;
+			onscreen = 0;
+		}else
+			bottomy = y;
+		if(i >= this->scrollStart && onscreen)
+			this->numVisible++;
+
+		if(e->type == MENUVAR){
+			int valwidth = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
+			if(valwidth > maxValWidth)
+				maxValWidth = valwidth;
+		}
+		if(e->r.w > maxNameWidth)
+			maxNameWidth = e->r.w;
+		i++;
+	}
+	if(this->r.w < maxNameWidth + maxValWidth + gap*fontscale)
+		this->r.w = maxNameWidth + maxValWidth + gap*fontscale;
+
+	this->scrollUpR = this->r;
+	this->scrollUpR.h = 16;
+	this->scrollDownR = this->scrollUpR;
+	this->scrollDownR.y = bottomy;
+
+	// Update active submenu
+	if(this->selectedEntry && this->selectedEntry->type == MENUSUB){
+		Menu *submenu = ((MenuEntry_Sub*)this->selectedEntry)->submenu;
+		submenu->r.x = this->r.x+this->r.w + 10;
+		submenu->r.y = this->r.y;
+		submenu->r.w = minwidth;	// update menu will expand
+		submenu->r.h = this->r.h;
+		submenu->update();
+	}
+}
+
+void
+Menu::draw(void)
+{
+	static char val[100];
+	int i;
+	MenuEntry *e;
+	i = 0;
+	for(e = this->entries; e; e = e->next){
+		if(i >= this->scrollStart+this->numVisible)
+			break;
+		if(i >= this->scrollStart){
+			int style = MENUFONT_NORMAL;
+			if(i == this->selection)
+				style = this == activeMenu ? MENUFONT_SEL_ACTIVE : MENUFONT_SEL_INACTIVE;
+			if(style != MENUFONT_SEL_ACTIVE && e == mouseOverEntry)
+				style = MENUFONT_MOUSE;
+			fontPrint(e->name, e->r.x, e->r.y, style);
+			if(e->type == MENUVAR){
+				int valw = fontGetLen(((MenuEntry_Var*)e)->getValWidth());
+				((MenuEntry_Var*)e)->getValStr(val, 100);
+				fontPrint(val, e->r.x+this->r.w-valw, e->r.y, style);
+			}
+		}
+		i++;
+	}
+
+	if(this->isScrollingUp)
+		drawArrow(this->scrollUpR, -1, isMouseInRect(this->scrollUpR));
+	if(this->isScrollingDown)
+		drawArrow(this->scrollDownR, 1, isMouseInRect(this->scrollDownR));
+
+	if(this->selectedEntry && this->selectedEntry->type == MENUSUB)
+		((MenuEntry_Sub*)this->selectedEntry)->submenu->draw();
+}
+
+Menu*
+findMenu(const char *name)
+{
+	Menu *m;
+	MenuEntry *e;
+	char *tmppath = strdup(name);
+	char *next, *curname;
+
+	curname = tmppath;
+	next = curname;
+
+	m = &toplevel;
+	while(*next){
+		curname = next;
+		while(*next){
+			if(*next == '|'){
+				*next++ = '\0';
+				break;
+			}
+			next++;
+		}
+		e = m->findEntry(curname);
+		if(e){
+			// return an error if the entry exists but isn't a menu
+			if(e->type != MENUSUB){
+				free(tmppath);
+				return nil;
+			}
+			m = ((MenuEntry_Sub*)e)->submenu;
+		}else{
+			// Create submenus that don't exist yet
+			Menu *submenu = new Menu();
+			submenu->parent = m;
+			MenuEntry *me = new MenuEntry_Sub(curname, submenu);
+			// Don't sort submenus outside the toplevel menu
+			if(m == &toplevel)
+				m->insertEntrySorted(me);
+			else
+				m->appendEntry(me);
+			m = submenu;
+		}
+	}
+
+	free(tmppath);
+	return m;
+}
+
+/*
+ * ****************
+ * debug menu
+ * ****************
+ */
+
+static uint8 cursorPx[] = {
+#include "cursor.inc"
+};
+
+static uint8 arrowPx[] = {
+#include "arrow.inc"
+};
+
+void
+initDebug(void)
+{
+	createMenuFont();
+
+	RwInt32 w, h, d, flags;
+	RwImage *img = RwImageCreate(16, 16, 32);
+	assert(img);
+	RwImageSetPixels(img, cursorPx);
+	RwImageSetStride(img, RwImageGetWidth(img)*4);
+	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
+	cursor = RwRasterCreate(w, h, d, flags);
+	cursor = RwRasterSetFromImage(cursor, img);
+	assert(cursor);
+	RwImageDestroy(img);
+
+	img = RwImageCreate(32, 16, 32);
+	assert(img);
+	RwImageSetPixels(img, arrowPx);
+	RwImageSetStride(img, RwImageGetWidth(img)*4);
+	RwImageFindRasterFormat(img, rwRASTERTYPETEXTURE, &w, &h, &d, &flags);
+	arrow = RwRasterCreate(w, h, d, flags);
+	arrow = RwRasterSetFromImage(arrow, img);
+	assert(arrow);
+	RwImageDestroy(img);
+}
+
+void
+processInput(void)
+{
+	int shift = KEYDOWN(rsRSHIFT) || KEYDOWN(rsLSHIFT);
+#define X(var, keycode) var = KEYJUSTDOWN(keycode);
+	MUHKEYS
+#undef X
+
+	// Implement auto-repeat
+#define X(var, keycode) \
+	if(var){ \
+		repeattime = downtime = CTimer::GetTimeInMilliseconds(); \
+		lastkeydown = keycode; \
+		keyptr = &var; \
+	}
+	MUHKEYS
+#undef X
+	if(lastkeydown){
+		if(KEYDOWN(lastkeydown)){
+			int curtime = CTimer::GetTimeInMilliseconds();
+			if(curtime - downtime > REPEATDELAY){
+				if(curtime - repeattime > REPEATINTERVAL){
+					repeattime = curtime;
+					*keyptr = 1;
+				}
+			}
+		}else{
+			lastkeydown = 0;
+		}
+	}
+
+	// Also for mouse buttons
+#define X(var, num)							  \
+	if(var){							  \
+		repeattime = downtime = CTimer::GetTimeInMilliseconds();	  \
+		lastbuttondown = num;					  \
+		buttonptr = &var;					  \
+	}
+	MUHBUTTONS
+#undef X
+	if(lastbuttondown){
+		if(buttondown[lastbuttondown-1]){
+			int curtime = CTimer::GetTimeInMilliseconds();
+			if(curtime - downtime > REPEATDELAY){
+				if(curtime - repeattime > REPEATINTERVAL){
+					repeattime = curtime;
+					*buttonptr = 1;
+				}
+			}
+		}else{
+			lastbuttondown = 0;
+		}
+	}
+
+	// Walk through all visible menus and figure out which one the mouse is over
+	mouseOverMenu = nil;
+	mouseOverEntry = nil;
+	Menu *menu;
+	for(menu = deepestMenu; menu; menu = menu->parent)
+		if(isMouseInRect(menu->r)){
+			mouseOverMenu = menu;
+			break;
+		}
+	if(mouseOverMenu){
+		// Walk all visibile entries and figure out which one the mouse is over
+		MenuEntry *e;
+		int i = 0;
+		for(e = mouseOverMenu->entries; e; e = e->next){
+			if(i >= mouseOverMenu->scrollStart+mouseOverMenu->numVisible)
+				break;
+			if(i >= mouseOverMenu->scrollStart){
+				RwRect r = e->r;
+				r.w = mouseOverMenu->r.w;	// span the whole menu
+				if(isMouseInRect(r)){
+					mouseOverEntry = e;
+					break;
+				}
+			}
+			i++;
+		}
+		if(mouseOverMenu->isScrollingUp && isMouseInRect(mouseOverMenu->scrollUpR)){
+			mouseOverEntry = &scrollUpEntry;
+			mouseOverEntry->r = mouseOverMenu->scrollUpR;
+			mouseOverEntry->menu = mouseOverMenu;
+			mouseOverEntry->type = MENUSCROLL;
+		}
+		if(mouseOverMenu->isScrollingDown && isMouseInRect(mouseOverMenu->scrollDownR)){
+			mouseOverEntry = &scrollDownEntry;
+			mouseOverEntry->r = mouseOverMenu->scrollDownR;
+			mouseOverEntry->menu = mouseOverMenu;
+			mouseOverEntry->type = MENUSCROLL;
+		}
+	}
+
+	if(pgupjustdown)
+		activeMenu->scroll(shift ? -5 : -1);
+	if(pgdnjustdown)
+		activeMenu->scroll(shift ? 5 : 1);
+	if(downjustdown)
+		activeMenu->changeSelection(activeMenu->selection + (shift ? 5 : 1));
+	if(upjustdown)
+		activeMenu->changeSelection(activeMenu->selection - (shift ? 5 : 1));
+
+	if(CPad::NewMouseControllerState.WHEELUP){
+		if(mouseOverMenu)
+			activeMenu = mouseOverMenu;
+		activeMenu->scroll(shift ? -5 : -1);
+	}
+	if(CPad::NewMouseControllerState.WHEELDN){
+		if(mouseOverMenu)
+			activeMenu = mouseOverMenu;
+		activeMenu->scroll(shift ? 5 : 1);
+	}
+
+	if(mouseOverEntry == &scrollUpEntry){
+		if(button1justdown){
+			activeMenu = mouseOverEntry->menu;
+			activeMenu->scroll(shift ? -5 : -1);
+		}
+	}
+	if(mouseOverEntry == &scrollDownEntry){
+		if(button1justdown){
+			activeMenu = mouseOverEntry->menu;
+			activeMenu->scroll(shift ? 5 : 1);
+		}
+	}
+
+	// Have to call this before processInput below because menu entry can change
+	if((button1justdown || button3justdown) && mouseOverEntry){
+		activeMenu = mouseOverEntry->menu;
+		activeMenu->changeSelection(mouseOverEntry);
+	}
+	if(KEYJUSTDOWN(rsENTER)){
+		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUSUB)
+			activeMenu = ((MenuEntry_Sub*)activeMenu->selectedEntry)->submenu;
+	}else if(KEYJUSTDOWN(rsBACKSP)){
+		if(activeMenu->parent)
+			activeMenu = activeMenu->parent;
+	}else{
+		if(mouseOverEntry && mouseOverEntry->type == MENUVAR)
+			((MenuEntry_Var*)mouseOverEntry)->processInput(true, mouseOverEntry == activeMenu->selectedEntry);
+		if(activeMenu->selectedEntry && activeMenu->selectedEntry->type == MENUVAR &&
+		   mouseOverEntry != activeMenu->selectedEntry)
+			((MenuEntry_Var*)activeMenu->selectedEntry)->processInput(false, true);
+	}
+}
+
+void
+updateMouse(void)
+{
+	CPad *pad = CPad::GetPad(0);
+	int dirX = 1;
+	int dirY = 1;
+
+	if(MousePointerStateHelper.bInvertHorizontally) dirX = -1;
+	if(MousePointerStateHelper.bInvertVertically) dirY = -1;
+
+	mouseX += pad->NewMouseControllerState.x*dirX;
+	mouseY += pad->NewMouseControllerState.y*dirY;
+
+	if(mouseX < 0.0f) mouseX = 0.0f;
+	if(mouseY < 0.0f) mouseY = 0.0f;
+	if(mouseX >= screenWidth) mouseX = screenWidth;
+	if(mouseY >= screenHeight) mouseY = screenHeight;
+
+	button1justdown = pad->NewMouseControllerState.LMB && !pad->OldMouseControllerState.LMB;
+	button2justdown = pad->NewMouseControllerState.MMB && !pad->OldMouseControllerState.MMB;
+	button3justdown = pad->NewMouseControllerState.RMB && !pad->OldMouseControllerState.RMB;
+	buttondown[0] = pad->NewMouseControllerState.LMB;
+	buttondown[1] = pad->NewMouseControllerState.MMB;
+	buttondown[2] = pad->NewMouseControllerState.RMB;
+
+	// Zero the mouse position so the camera won't move
+	pad->NewMouseControllerState.x = 0.0f;
+	pad->NewMouseControllerState.y = 0.0f;
+}
+
+void
+DebugMenuProcess(void)
+{
+	// We only process some input here
+
+	CPad *pad = CPad::GetPad(0);
+	if(CTRLJUSTDOWN('M'))
+		menuOn = !menuOn;
+	if(!menuOn)
+		return;
+
+	pad->DisablePlayerControls = 1;
+	// TODO: this could happen earlier
+	if(!menuInitialized){
+		initDebug();
+		menuInitialized = 1;
+	}
+	updateMouse();
+
+}
+
+#ifdef LIBRW
+#define CURRENTCAM (rw::engine->currentCamera)
+#else
+#define CURRENTCAM ((RwCamera*)RWSRCGLOBAL(curCamera))
+#endif
+
+void
+DebugMenuRender(void)
+{
+	if(!menuOn)
+		return;
+
+	RwRenderStateSet(rwRENDERSTATEZTESTENABLE, 0);
+	RwRenderStateSet(rwRENDERSTATEZWRITEENABLE, 0);
+	RwRenderStateSet(rwRENDERSTATEVERTEXALPHAENABLE, (void*)TRUE);
+	RwRenderStateSet(rwRENDERSTATESRCBLEND, (void*)rwBLENDSRCALPHA);
+	RwRenderStateSet(rwRENDERSTATEDESTBLEND, (void*)rwBLENDINVSRCALPHA);
+	RwRenderStateSet(rwRENDERSTATEFOGENABLE, 0);
+	RwRenderStateSet(rwRENDERSTATECULLMODE, (void*)rwCULLMODECULLNONE);
+
+	RwCamera *cam = CURRENTCAM;
+	screenWidth = RwRasterGetWidth(RwCameraGetRaster(cam));
+	screenHeight = RwRasterGetHeight(RwCameraGetRaster(cam));
+
+//	if(screenHeight > 1080)
+//		fontscale = 2;
+//	else
+		fontscale = 1;
+
+	Pt sz;
+	sz = fontPrint("Debug Menu", firstBorder*fontscale+30, topBorder, 0);
+
+	toplevel.r.x = firstBorder*fontscale;
+	toplevel.r.y = topBorder + sz.y + 10;
+	toplevel.r.w = minwidth;	// update menu will expand
+	toplevel.r.h = screenHeight - 10 - toplevel.r.y;
+	toplevel.update();
+	toplevel.draw();
+	processInput();
+	RtCharsetBufferFlush();
+
+	drawMouse();
+}
+
+
+
+void
+drawArrow(RwRect r, int direction, int style)
+{
+	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
+	static RwIm2DVertex arrowVerts[4];
+
+	RwCamera *cam = CURRENTCAM;
+	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);
+
+	int width = RwRasterGetWidth(arrow);
+	int height = RwRasterGetHeight(arrow);
+
+	int left = r.x + (r.w - width)/2;
+	int right = left + width;
+	int top = r.y;
+	int bottom = r.y+r.h;
+
+	float umin = 0.5f / width;
+	float vmin = 0.5f / height;
+	float umax = (width + 0.5f) / width;
+	float vmax = (height + 0.5f) / height;
+	if(direction < 0){
+		vmin = (height - 0.5f) / height;
+		vmax = -0.5f / height;
+	}
+
+	if(style){
+		RwIm2DVertexSetScreenX(&arrowVerts[0], r.x);
+		RwIm2DVertexSetScreenY(&arrowVerts[0], r.y-1);
+		RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
+		RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
+		RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
+		RwIm2DVertexSetIntRGBA(&arrowVerts[0], 132, 132, 132, 255);
+
+		RwIm2DVertexSetScreenX(&arrowVerts[1], r.x+r.w);
+		RwIm2DVertexSetScreenY(&arrowVerts[1], r.y-1);
+		RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
+		RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
+		RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
+		RwIm2DVertexSetIntRGBA(&arrowVerts[1], 132, 132, 132, 255);
+
+		RwIm2DVertexSetScreenX(&arrowVerts[2], r.x);
+		RwIm2DVertexSetScreenY(&arrowVerts[2], r.y+r.h+1);
+		RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
+		RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
+		RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
+		RwIm2DVertexSetIntRGBA(&arrowVerts[2], 132, 132, 132, 255);
+
+		RwIm2DVertexSetScreenX(&arrowVerts[3], r.x+r.w);
+		RwIm2DVertexSetScreenY(&arrowVerts[3], r.y+r.h+1);
+		RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
+		RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
+		RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
+		RwIm2DVertexSetIntRGBA(&arrowVerts[3], 132, 132, 132, 255);
+
+		RwRenderStateSet(rwRENDERSTATETEXTURERASTER, nil);
+		RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+		RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
+	}
+
+
+	RwIm2DVertexSetScreenX(&arrowVerts[0], left);
+	RwIm2DVertexSetScreenY(&arrowVerts[0], top);
+	RwIm2DVertexSetScreenZ(&arrowVerts[0], RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(&arrowVerts[0], RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(&arrowVerts[0], recipz);
+	RwIm2DVertexSetIntRGBA(&arrowVerts[0], 255, 255, 255, 255);
+	RwIm2DVertexSetU(&arrowVerts[0], umin, recipz);
+	RwIm2DVertexSetV(&arrowVerts[0], vmin, recipz);
+
+	RwIm2DVertexSetScreenX(&arrowVerts[1], right);
+	RwIm2DVertexSetScreenY(&arrowVerts[1], top);
+	RwIm2DVertexSetScreenZ(&arrowVerts[1], RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(&arrowVerts[1], RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(&arrowVerts[1], recipz);
+	RwIm2DVertexSetIntRGBA(&arrowVerts[1], 255, 255, 255, 255);
+	RwIm2DVertexSetU(&arrowVerts[1], umax, recipz);
+	RwIm2DVertexSetV(&arrowVerts[1], vmin, recipz);
+
+	RwIm2DVertexSetScreenX(&arrowVerts[2], left);
+	RwIm2DVertexSetScreenY(&arrowVerts[2], bottom);
+	RwIm2DVertexSetScreenZ(&arrowVerts[2], RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(&arrowVerts[2], RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(&arrowVerts[2], recipz);
+	RwIm2DVertexSetIntRGBA(&arrowVerts[2], 255, 255, 255, 255);
+	RwIm2DVertexSetU(&arrowVerts[2], umin, recipz);
+	RwIm2DVertexSetV(&arrowVerts[2], vmax, recipz);
+
+	RwIm2DVertexSetScreenX(&arrowVerts[3], right);
+	RwIm2DVertexSetScreenY(&arrowVerts[3], bottom);
+	RwIm2DVertexSetScreenZ(&arrowVerts[3], RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(&arrowVerts[3], RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(&arrowVerts[3], recipz);
+	RwIm2DVertexSetIntRGBA(&arrowVerts[3], 255, 255, 255, 255);
+	RwIm2DVertexSetU(&arrowVerts[3], umax, recipz);
+	RwIm2DVertexSetV(&arrowVerts[3], vmax, recipz);
+
+	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, arrow);
+	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, arrowVerts, 4, indices, 6);
+}
+
+void
+drawMouse(void)
+{
+	static RwImVertexIndex indices[] = { 0, 1, 2, 2, 1, 3 };
+	static RwIm2DVertex vertices[4];
+	RwIm2DVertex *vert;
+	RwCamera *cam;
+	cam = CURRENTCAM;
+	float x = mouseX;
+	float y = mouseY;
+	float w = RwRasterGetWidth(cursor);
+	float h = RwRasterGetHeight(cursor);
+	float recipz = 1.0f/RwCameraGetNearClipPlane(cam);
+
+	float umin = 0.5f / w;
+	float vmin = 0.5f / h;
+	float umax = (w + 0.5f) / w;
+	float vmax = (h + 0.5f) / h;
+
+	vert = vertices;
+	RwIm2DVertexSetScreenX(vert, x);
+	RwIm2DVertexSetScreenY(vert, y);
+	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(vert, recipz);
+	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
+	RwIm2DVertexSetU(vert, umin, recipz);
+	RwIm2DVertexSetV(vert, vmin, recipz);
+	vert++;
+
+	RwIm2DVertexSetScreenX(vert, x+w);
+	RwIm2DVertexSetScreenY(vert, y);
+	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(vert, recipz);
+	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
+	RwIm2DVertexSetU(vert, umax, recipz);
+	RwIm2DVertexSetV(vert, vmin, recipz);
+	vert++;
+
+	RwIm2DVertexSetScreenX(vert, x);
+	RwIm2DVertexSetScreenY(vert, y+h);
+	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(vert, recipz);
+	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
+	RwIm2DVertexSetU(vert, umin, recipz);
+	RwIm2DVertexSetV(vert, vmax, recipz);
+	vert++;
+
+	RwIm2DVertexSetScreenX(vert, x+w);
+	RwIm2DVertexSetScreenY(vert, y+h);
+	RwIm2DVertexSetScreenZ(vert, RwIm2DGetNearScreenZ());
+	RwIm2DVertexSetCameraZ(vert, RwCameraGetNearClipPlane(cam));
+	RwIm2DVertexSetRecipCameraZ(vert, recipz);
+	RwIm2DVertexSetIntRGBA(vert, 255, 255, 255, 255);
+	RwIm2DVertexSetU(vert, umax, recipz);
+	RwIm2DVertexSetV(vert, vmax, recipz);
+	vert++;
+
+	RwRenderStateSet(rwRENDERSTATETEXTURERASTER, cursor);
+	RwRenderStateSet(rwRENDERSTATETEXTUREFILTER, (void*)rwFILTERLINEAR);
+	RwIm2DRenderIndexedPrimitive(rwPRIMTYPETRILIST, vertices, 4, indices, 6);
+}
+
+
+
+
+/*
+ * Generate interfaces
+ */
+
+
+#define X(NAME, TYPE, unused1, unused2) \
+MenuEntry*																			\
+DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound, const char **strings)	\
+{																				\
+	Menu *m = findMenu(path);																\
+	if(m == nil)																		\
+		return nil;																		\
+	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound, strings);							\
+	m->appendEntry(e);							\
+	return e;																		\
+}
+INTTYPES
+#undef X
+
+#define X(NAME, TYPE, unused1, unused2) \
+MenuEntry*																			\
+DebugMenuAdd##NAME(const char *path, const char *name, TYPE *ptr, TriggerFunc triggerFunc, TYPE step, TYPE lowerBound, TYPE upperBound)	\
+{																				\
+	Menu *m = findMenu(path);																\
+	if(m == nil)																		\
+		return nil;																		\
+	MenuEntry *e = new MenuEntry_##NAME(name, ptr, triggerFunc, step, lowerBound, upperBound);								\
+	m->appendEntry(e);							\
+	return e;																		\
+}
+FLOATTYPES
+#undef X
+
+MenuEntry*																			\
+DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc)
+{
+	Menu *m = findMenu(path);
+	if(m == nil)
+		return nil;
+	MenuEntry *e = new MenuEntry_Cmd(name, triggerFunc);
+	m->appendEntry(e);
+	return e;
+}
+
+void
+DebugMenuEntrySetWrap(MenuEntry *e, bool wrap)
+{
+	if(e && e->type == MENUVAR)
+		((MenuEntry_Var*)e)->wrapAround = wrap;
+}
+
+void
+DebugMenuEntrySetStrings(MenuEntry *e, const char **strings)
+{
+	if(e && e->type == MENUVAR_INT)
+		((MenuEntry_Int*)e)->setStrings(strings);
+}
+
+void
+DebugMenuEntrySetAddress(MenuEntry *e, void *addr)
+{
+	if(e && e->type == MENUVAR){
+		MenuEntry_Var *ev = (MenuEntry_Var*)e;
+		// HACK - we know the variable field is at the same address
+		// for all int/float classes. let's hope it stays that way.
+		if(ev->vartype = MENUVAR_INT)
+			((MenuEntry_Int32*)e)->variable = (int32*)addr;
+		else if(ev->vartype = MENUVAR_FLOAT)
+			((MenuEntry_Float32*)e)->variable = (float*)addr;
+	}
+}
diff --git a/src/extras/debugmenu.h b/src/extras/debugmenu.h
new file mode 100644
index 00000000..29bbd972
--- /dev/null
+++ b/src/extras/debugmenu.h
@@ -0,0 +1,80 @@
+#pragma once
+
+typedef void (*TriggerFunc)(void);
+
+struct Menu;
+
+struct MenuEntry
+{
+	int type;
+	const char *name;
+	MenuEntry *next;
+	RwRect r;
+	Menu *menu;
+
+	MenuEntry(const char *name);
+};
+
+typedef MenuEntry DebugMenuEntry;
+
+MenuEntry *DebugMenuAddInt8(const char *path, const char *name, int8 *ptr, TriggerFunc triggerFunc, int8 step, int8 lowerBound, int8 upperBound, const char **strings);
+MenuEntry *DebugMenuAddInt16(const char *path, const char *name, int16 *ptr, TriggerFunc triggerFunc, int16 step, int16 lowerBound, int16 upperBound, const char **strings);
+MenuEntry *DebugMenuAddInt32(const char *path, const char *name, int32 *ptr, TriggerFunc triggerFunc, int32 step, int32 lowerBound, int32 upperBound, const char **strings);
+MenuEntry *DebugMenuAddInt64(const char *path, const char *name, int64 *ptr, TriggerFunc triggerFunc, int64 step, int64 lowerBound, int64 upperBound, const char **strings);
+MenuEntry *DebugMenuAddUInt8(const char *path, const char *name, uint8 *ptr, TriggerFunc triggerFunc, uint8 step, uint8 lowerBound, uint8 upperBound, const char **strings);
+MenuEntry *DebugMenuAddUInt16(const char *path, const char *name, uint16 *ptr, TriggerFunc triggerFunc, uint16 step, uint16 lowerBound, uint16 upperBound, const char **strings);
+MenuEntry *DebugMenuAddUInt32(const char *path, const char *name, uint32 *ptr, TriggerFunc triggerFunc, uint32 step, uint32 lowerBound, uint32 upperBound, const char **strings);
+MenuEntry *DebugMenuAddUInt64(const char *path, const char *name, uint64 *ptr, TriggerFunc triggerFunc, uint64 step, uint64 lowerBound, uint64 upperBound, const char **strings);
+MenuEntry *DebugMenuAddFloat32(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound);
+MenuEntry *DebugMenuAddFloat64(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound);
+MenuEntry *DebugMenuAddCmd(const char *path, const char *name, TriggerFunc triggerFunc);
+void DebugMenuEntrySetWrap(MenuEntry *e, bool wrap);
+void DebugMenuEntrySetStrings(MenuEntry *e, const char **strings);
+void DebugMenuEntrySetAddress(MenuEntry *e, void *addr);
+void DebugMenuProcess(void);
+void DebugMenuRender(void);
+
+
+// Some overloads for simplicity
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc, int8_t step, int8_t lowerBound, int8_t upperBound, const char **strings)
+{ return DebugMenuAddInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc, int16_t step, int16_t lowerBound, int16_t upperBound, const char **strings)
+{ return DebugMenuAddInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc, int32_t step, int32_t lowerBound, int32_t upperBound, const char **strings)
+{ return DebugMenuAddInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, int64_t *ptr, TriggerFunc triggerFunc, int64_t step, int64_t lowerBound, int64_t upperBound, const char **strings)
+{ return DebugMenuAddInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint8_t *ptr, TriggerFunc triggerFunc, uint8_t step, uint8_t lowerBound, uint8_t upperBound, const char **strings)
+{ return DebugMenuAddUInt8(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint16_t *ptr, TriggerFunc triggerFunc, uint16_t step, uint16_t lowerBound, uint16_t upperBound, const char **strings)
+{ return DebugMenuAddUInt16(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint32_t *ptr, TriggerFunc triggerFunc, uint32_t step, uint32_t lowerBound, uint32_t upperBound, const char **strings)
+{ return DebugMenuAddUInt32(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, uint64_t *ptr, TriggerFunc triggerFunc, uint64_t step, uint64_t lowerBound, uint64_t upperBound, const char **strings)
+{ return DebugMenuAddUInt64(path, name, ptr, triggerFunc, step, lowerBound, upperBound, strings); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, float *ptr, TriggerFunc triggerFunc, float step, float lowerBound, float upperBound)
+{ return DebugMenuAddFloat32(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+inline DebugMenuEntry *DebugMenuAddVar(const char *path, const char *name, double *ptr, TriggerFunc triggerFunc, double step, double lowerBound, double upperBound)
+{ return DebugMenuAddFloat64(path, name, ptr, triggerFunc, step, lowerBound, upperBound); }
+
+inline DebugMenuEntry *DebugMenuAddVarBool32(const char *path, const char *name, int32_t *ptr, TriggerFunc triggerFunc)
+{
+	static const char *boolstr[] = { "Off", "On" };
+	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+	DebugMenuEntrySetWrap(e, true);
+	return e;
+}
+inline DebugMenuEntry *DebugMenuAddVarBool16(const char *path, const char *name, int16_t *ptr, TriggerFunc triggerFunc)
+{
+	static const char *boolstr[] = { "Off", "On" };
+	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+	DebugMenuEntrySetWrap(e, true);
+	return e;
+}
+inline DebugMenuEntry *DebugMenuAddVarBool8(const char *path, const char *name, int8_t *ptr, TriggerFunc triggerFunc)
+{
+	static const char *boolstr[] = { "Off", "On" };
+	DebugMenuEntry *e = DebugMenuAddVar(path, name, ptr, triggerFunc, 1, 0, 1, boolstr);
+	DebugMenuEntrySetWrap(e, true);
+	return e;
+}
diff --git a/src/extras/inttypes.h b/src/extras/inttypes.h
new file mode 100644
index 00000000..bf0c53e2
--- /dev/null
+++ b/src/extras/inttypes.h
@@ -0,0 +1,216 @@
+#define PRId8     "hhd"
+#define PRId16    "hd"
+#define PRId32    "ld"
+#define PRId64    "lld"
+
+#define PRIdFAST8    "hhd"
+#define PRIdFAST16    "hd"
+#define PRIdFAST32    "ld"
+#define PRIdFAST64   "lld"
+
+#define PRIdLEAST8   "hhd"
+#define PRIdLEAST16  "hd"
+#define PRIdLEAST32  "ld"
+#define PRIdLEAST64  "lld"
+
+#define PRIdMAX   "lld"
+#define PRIdPTR   "lld"
+
+#define PRIi8     "hhi"
+#define PRIi16    "hi"
+#define PRIi32    "li"
+#define PRIi64    "lli"
+
+#define PRIiFAST8    "hhi"
+#define PRIiFAST16    "hi"
+#define PRIiFAST32    "li"
+#define PRIiFAST64    "lli"
+
+#define PRIiLEAST8   "hhi"
+#define PRIiLEAST16  "hi"
+#define PRIiLEAST32  "li"
+#define PRIiLEAST64  "lli"
+
+#define PRIiMAX   "lli"
+#define PRIiPTR   "lli"
+
+#define PRIo8     "hho"
+#define PRIo16    "ho"
+#define PRIo32    "lo"
+#define PRIo64    "llo"
+
+#define PRIoFAST8    "hho"
+#define PRIoFAST16    "ho"
+#define PRIoFAST32    "lo"
+#define PRIoFAST64   "llo"
+
+#define PRIoLEAST8   "hho"
+#define PRIoLEAST16  "ho"
+#define PRIoLEAST32  "lo"
+#define PRIoLEAST64  "llo"
+
+#define PRIoMAX   "llo"
+#define PRIoPTR   "llo"
+
+#define PRIu8     "hhu"
+#define PRIu16    "hu"
+#define PRIu32    "lu"
+#define PRIu64    "llu"
+
+#define PRIuFAST8    "hhu"
+#define PRIuFAST16    "hu"
+#define PRIuFAST32    "lu"
+#define PRIuFAST64   "llu"
+
+#define PRIuLEAST8   "hhu"
+#define PRIuLEAST16  "hu"
+#define PRIuLEAST32  "lu"
+#define PRIuLEAST64  "llu"
+
+#define PRIuMAX   "llu"
+#define PRIuPTR   "llu"
+
+#define PRIx8     "hhx"
+#define PRIx16    "hx"
+#define PRIx32    "lx"
+#define PRIx64    "llx"
+
+#define PRIxFAST8    "hhx"
+#define PRIxFAST16    "hx"
+#define PRIxFAST32    "lx"
+#define PRIxFAST64   "llx"
+
+#define PRIxLEAST8   "hhx"
+#define PRIxLEAST16  "hx"
+#define PRIxLEAST32  "lx"
+#define PRIxLEAST64  "llx"
+
+#define PRIxMAX   "llx"
+#define PRIxPTR   "llx"
+
+#define PRIX8     "hhX"
+#define PRIX16    "hX"
+#define PRIX32    "lX"
+#define PRIX64    "llX"
+
+#define PRIXFAST8    "hhX"
+#define PRIXFAST16    "hX"
+#define PRIXFAST32    "lX"
+#define PRIXFAST64   "llX"
+
+#define PRIXLEAST8   "hhX"
+#define PRIXLEAST16  "hX"
+#define PRIXLEAST32  "lX"
+#define PRIXLEAST64  "llX"
+
+#define PRIXMAX   "llX"
+#define PRIXPTR   "llX"
+
+        /* SCAN FORMAT MACROS */
+#define SCNd8     "hhd"
+#define SCNd16    "hd"
+#define SCNd32    "ld"
+#define SCNd64    "lld"
+
+#define SCNdFAST8    "hhd"
+#define SCNdFAST16    "hd"
+#define SCNdFAST32    "ld"
+#define SCNdFAST64   "lld"
+
+#define SCNdLEAST8   "hhd"
+#define SCNdLEAST16  "hd"
+#define SCNdLEAST32  "ld"
+#define SCNdLEAST64  "lld"
+
+#define SCNdMAX   "lld"
+#define SCNdPTR   "lld"
+
+#define SCNi8     "hhi"
+#define SCNi16    "hi"
+#define SCNi32    "li"
+#define SCNi64    "lli"
+
+#define SCNiFAST8    "hhi"
+#define SCNiFAST16    "hi"
+#define SCNiFAST32    "li"
+#define SCNiFAST64   "lli"
+
+#define SCNiLEAST8   "hhi"
+#define SCNiLEAST16  "hi"
+#define SCNiLEAST32  "li"
+#define SCNiLEAST64  "lli"
+
+#define SCNiMAX   "lli"
+#define SCNiPTR   "lli"
+
+#define SCNo8     "hho"
+#define SCNo16    "ho"
+#define SCNo32    "lo"
+#define SCNo64    "llo"
+
+#define SCNoFAST8    "hho"
+#define SCNoFAST16    "ho"
+#define SCNoFAST32    "lo"
+#define SCNoFAST64   "llo"
+
+#define SCNoLEAST8   "hho"
+#define SCNoLEAST16  "ho"
+#define SCNoLEAST32  "lo"
+#define SCNoLEAST64  "llo"
+
+#define SCNoMAX   "llo"
+#define SCNoPTR   "llo"
+
+#define SCNu8     "hhu"
+#define SCNu16    "hu"
+#define SCNu32    "lu"
+#define SCNu64    "llu"
+
+#define SCNuFAST8    "hhu"
+#define SCNuFAST16    "hu"
+#define SCNuFAST32    "lu"
+#define SCNuFAST64   "llu"
+
+#define SCNuLEAST8   "hhu"
+#define SCNuLEAST16  "hu"
+#define SCNuLEAST32  "lu"
+#define SCNuLEAST64  "llu"
+
+#define SCNuMAX   "llu"
+#define SCNuPTR   "llu"
+
+#define SCNx8     "hhx"
+#define SCNx16    "hx"
+#define SCNx32    "lx"
+#define SCNx64    "llx"
+
+#define SCNxFAST8    "hhx"
+#define SCNxFAST16    "hx"
+#define SCNxFAST32    "lx"
+#define SCNxFAST64   "llx"
+
+#define SCNxLEAST8   "hhx"
+#define SCNxLEAST16  "hx"
+#define SCNxLEAST32  "lx"
+#define SCNxLEAST64  "llx"
+
+#define SCNxMAX   "llx"
+#define SCNxPTR   "llx"
+
+#define SCNX8     "hhX"
+#define SCNX16    "hX"
+#define SCNX32    "lX"
+#define SCNX64    "llX"
+
+#define SCNXFAST8    "hhX"
+#define SCNXFAST16    "hX"
+#define SCNXFAST32    "lX"
+#define SCNXFAST64   "llX"
+
+#define SCNXLEAST8   "hhX"
+#define SCNXLEAST16  "hX"
+#define SCNXLEAST32  "lX"
+#define SCNXLEAST64  "llX"
+
+#define SCNXMAX   "llX"
+#define SCNXPTR   "llX"
\ No newline at end of file
diff --git a/src/fakerw/fake.cpp b/src/fakerw/fake.cpp
index 59c01c91..b86e9214 100644
--- a/src/fakerw/fake.cpp
+++ b/src/fakerw/fake.cpp
@@ -176,8 +176,8 @@ const RwChar *RwImageSetPath(const RwChar * path) { Image::setSearchPath(path);
 RwImage     *RwImageSetStride(RwImage * image, RwInt32 stride) { image->stride = stride; return image; }
 RwImage     *RwImageSetPixels(RwImage * image, RwUInt8 * pixels) { image->pixels = pixels; return image; }
 RwImage     *RwImageSetPalette(RwImage * image, RwRGBA * palette) { image->palette = (uint8*)palette; return image; }
-RwInt32      RwImageGetWidth(const RwImage * image);
-RwInt32      RwImageGetHeight(const RwImage * image);
+RwInt32      RwImageGetWidth(const RwImage * image) { return image->width; }
+RwInt32      RwImageGetHeight(const RwImage * image) { return image->height; }
 RwInt32      RwImageGetDepth(const RwImage * image);
 RwInt32      RwImageGetStride(const RwImage * image);
 RwUInt8     *RwImageGetPixels(const RwImage * image);
diff --git a/src/rw/RwHelper.cpp b/src/rw/RwHelper.cpp
index 5aa4475f..02a07558 100644
--- a/src/rw/RwHelper.cpp
+++ b/src/rw/RwHelper.cpp
@@ -9,12 +9,20 @@
 RtCharset *debugCharset;
 #endif
 
+static bool charsetOpen;
+void OpenCharsetSafe()
+{
+	if(!charsetOpen)
+		RtCharsetOpen();
+	charsetOpen = true;
+}
+
 void CreateDebugFont()
 {
 #ifndef FINAL
 	RwRGBA color = { 255, 255, 128, 255 };
 	RwRGBA colorbg = { 0, 0, 0, 0 };
-	RtCharsetOpen();
+	OpenCharsetSafe();
 	debugCharset = RtCharsetCreate(&color, &colorbg);
 #endif
 }
@@ -24,6 +32,7 @@ void DestroyDebugFont()
 #ifndef FINAL
 	RtCharsetDestroy(debugCharset);
 	RtCharsetClose();
+	charsetOpen = false;
 #endif
 }
 
diff --git a/src/rw/RwHelper.h b/src/rw/RwHelper.h
index 9f178ec2..e6740031 100644
--- a/src/rw/RwHelper.h
+++ b/src/rw/RwHelper.h
@@ -3,6 +3,7 @@
 void *RwMallocAlign(RwUInt32 size, RwUInt32 align);
 void RwFreeAlign(void *mem);
 
+void OpenCharsetSafe();
 void CreateDebugFont();
 void DestroyDebugFont();
 void ObrsPrintfString(const char *str, short x, short y);