diff --git a/source/common/engine/namedef.h b/source/common/engine/namedef.h
index e8e63c0e7..64d8c5490 100644
--- a/source/common/engine/namedef.h
+++ b/source/common/engine/namedef.h
@@ -1097,3 +1097,6 @@ xy(menu_clear, "menu/clear")
 xy(menu_dismiss, "menu/dismiss")
 xy(menu_change, "menu/change")
 xy(menu_advance, "menu/advance")
+
+xx(zoomsize)
+
diff --git a/source/core/gamecontrol.cpp b/source/core/gamecontrol.cpp
index 306d5dfb2..8513a9e88 100644
--- a/source/core/gamecontrol.cpp
+++ b/source/core/gamecontrol.cpp
@@ -1796,27 +1796,6 @@ void playerProcessHelpers(fixed_t* q16ang, double* angAdjust, fixed_t* angTarget
 	}
 }
 
-void GameInterface::DrawCenteredTextScreen(const DVector2& origin, const char* text, int position, bool bg)
-{
-	double scale = SmallFontScale();
-	int formatwidth = int(320 / scale);
-	auto lines = V_BreakLines(SmallFont, formatwidth, text, true);
-	auto fheight = bg ? 10 : SmallFont->GetHeight() * scale;	// Fixme: Get spacing for text pages from elsewhere.
-	if (!bg)
-	{
-		auto totaltextheight = lines.Size() * fheight;
-		position -= totaltextheight / 2;
-	}
-
-	double y = origin.Y + position;
-	for (auto& line : lines)
-	{
-		double x = origin.X + 160 - line.Width * scale * 0.5;
-		DrawText(twod, SmallFont, CR_UNTRANSLATED, x, y, line.Text, DTA_FullscreenScale, FSMode_Fit320x200, DTA_ScaleX, scale, DTA_ScaleY, scale, TAG_DONE);
-		y += fheight;
-	}
-}
-
 bool M_Active()
 {
 	return CurrentMenu != nullptr || ConsoleState == c_down || ConsoleState == c_falling;
diff --git a/source/core/gamestruct.h b/source/core/gamestruct.h
index e7e05a2e3..bdeb4e1ed 100644
--- a/source/core/gamestruct.h
+++ b/source/core/gamestruct.h
@@ -56,7 +56,6 @@ struct GameInterface
 	virtual void FreeGameData() {}
 	virtual void PlayHudSound() {}
 	virtual GameStats getStats() { return {}; }
-	virtual void DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) {}
 	virtual void MainMenuOpened() {}
 	virtual void MenuOpened() {}
 	virtual void MenuClosed() {}
@@ -66,7 +65,6 @@ struct GameInterface
 	virtual bool StartGame(FNewGameStartup& gs) { return false; }
 	virtual FSavegameInfo GetSaveSig() { return { "", 0, 0}; }
 	virtual bool DrawSpecialScreen(const DVector2 &origin, int tilenum) { return false; }
-	virtual void DrawCenteredTextScreen(const DVector2& origin, const char* text, int position, bool withbg = true);
 	virtual double SmallFontScale() { return 1; }
 	virtual bool SaveGame() { return true; }
 	virtual bool LoadGame() { return true; }
diff --git a/source/exhumed/src/d_menu.cpp b/source/exhumed/src/d_menu.cpp
index 96ccd702d..5dd6e92cc 100644
--- a/source/exhumed/src/d_menu.cpp
+++ b/source/exhumed/src/d_menu.cpp
@@ -31,109 +31,34 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 #include "mapinfo.h"
 #include "gamecontrol.h"
 #include "v_draw.h"
-
-
-#include "razemenu.h"	// to override the local menu.h
+#include "vm.h"
+#include "razemenu.h"
 
 #include "../../glbackend/glbackend.h"
 
 
 BEGIN_PS_NS
 
-//----------------------------------------------------------------------------
-//
-// Implements the native looking menu used for the main menu
-// and the episode/skill selection screens, i.e. the parts
-// that need to look authentic
-//
-//----------------------------------------------------------------------------
-void menu_DoPlasma();
-double zoomsize = 0;
 
-#if 0
-class PSMainMenu : public DListMenu
+DEFINE_ACTION_FUNCTION(_ListMenuItemExhumedPlasma, Draw)
 {
-
-	void Init(DMenu* parent, FListMenuDescriptor* desc) override
-	{
-		DListMenu::Init(parent, desc);
-		PlayLocalSound(StaticSound[kSound31], 0, false, CHANF_UI);
-	}
-
-	void Ticker() override
-	{
-		// handle the menu zoom-in
-		if (zoomsize < 1.)
-		{
-			zoomsize += 0.0625;
-			if (zoomsize >= 1.) {
-				zoomsize = 1.;
-			}
-		}
-	}
-
-	void PreDraw() override
-	{
-		if (mDesc->mMenuName == NAME_Mainmenu)
-			menu_DoPlasma();
-		else
-		{
-			auto nLogoTile = EXHUMED ? kExhumedLogo : kPowerslaveLogo;
-			DrawRel(nLogoTile, 160, 40);
-		}
-	}
-};
-#endif
-
-
-//----------------------------------------------------------------------------
-//
-// Menu related game interface functions
-//
-//----------------------------------------------------------------------------
-
-void GameInterface::DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags)
-{
-#if 0
-	int tilenum = (int)strtoll(text, nullptr, 0);
-	double y = ypos - tilesiz[tilenum].y / 2;
-
-	int8_t shade;
-
-	if (state == NIT_SelectedState) 
-	{ // currently selected menu item
-		shade = Sin(I_GetBuildTime() << 4) >> 9;
-	}
-	else if (state == NIT_ActiveState) {
-		shade = 0;
-	}
-	else {
-		shade = 25;
-	}
-
-	// Todo: Replace the boxes with an empty one and draw the text with a font.
-	auto tex = tileGetTexture(tilenum);
-
-	DrawTexture(twod, tex, 160, y + tex->GetDisplayHeight(), DTA_FullscreenScale, FSMode_Fit320x200, DTA_CenterOffset, true, DTA_ScaleX, zoomsize, DTA_ScaleY, zoomsize, 
-		DTA_Color, shadeToLight(shade), TAG_DONE);
-
-	// tilesizx is 51
-	// tilesizy is 33
-
-	if (state == NIT_SelectedState)
-	{
-		tex = tileGetTexture(kMenuCursorTile);
-		DrawTexture(twod, tex, 62, ypos - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, TAG_DONE);
-		DrawTexture(twod, tex, 207, ypos - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, DTA_FlipX, true, TAG_DONE);
-	}
-#endif
+	menu_DoPlasma();
+	return 0;
 }
 
+DEFINE_ACTION_FUNCTION(_ListMenuItemExhumedLogo, Draw)
+{
+	auto nLogoTile = EXHUMED ? kExhumedLogo : kPowerslaveLogo;
+	DrawRel(nLogoTile, 160, 40);
+	return 0;
+}
+
+
 
 void GameInterface::MenuOpened()
 {
 	GrabPalette();
-	zoomsize = 0;
+	menuDelegate->FloatVar(NAME_zoomsize) = 0;
 	StopAllSounds();
 	StopLocalSound();
 }
@@ -142,17 +67,21 @@ void GameInterface::MenuSound(EMenuSounds snd)
 {
 	switch (snd)
 	{
-		case CursorSound:
-			PlayLocalSound(StaticSound[kSound35], 0, false, CHANF_UI);
-			break;
+	case ActivateSound:
+		PlayLocalSound(StaticSound[kSound31], 0, false, CHANF_UI);
+		break;
 
-		case AdvanceSound:
-		case BackSound:
-			PlayLocalSound(StaticSound[kSound33], 0, false, CHANF_UI);
-			break;
+	case CursorSound:
+		PlayLocalSound(StaticSound[kSound35], 0, false, CHANF_UI);
+		break;
 
-		default:
-			return;
+	case AdvanceSound:
+	case BackSound:
+		PlayLocalSound(StaticSound[kSound33], 0, false, CHANF_UI);
+		break;
+
+	default:
+		return;
 	}
 }
 
diff --git a/source/exhumed/src/exhumed.h b/source/exhumed/src/exhumed.h
index 7f287d578..338a68898 100644
--- a/source/exhumed/src/exhumed.h
+++ b/source/exhumed/src/exhumed.h
@@ -237,7 +237,6 @@ struct GameInterface : ::GameInterface
     void app_init() override;
     void clearlocalinputstate() override;
 	bool GenerateSavePic() override;
-    void DrawNativeMenuText(int fontnum, int state, double xpos, double ypos, float fontscale, const char* text, int flags) override;
     void MenuOpened() override;
     void MenuSound(EMenuSounds snd) override;
     void MenuClosed() override;
diff --git a/source/games/duke/src/d_menu.cpp b/source/games/duke/src/d_menu.cpp
index 902566378..cab642e94 100644
--- a/source/games/duke/src/d_menu.cpp
+++ b/source/games/duke/src/d_menu.cpp
@@ -157,16 +157,6 @@ FSavegameInfo GameInterface::GetSaveSig()
 	return { SAVESIG_DN3D, MINSAVEVER_DN3D, SAVEVER_DN3D };
 }
 
-void GameInterface::DrawCenteredTextScreen(const DVector2 &origin, const char *text, int position, bool bg)
-{
-	if (bg) Menu_DrawBackground(origin);
-	else if (!isRR())
-	{
-		//Menu_DrawCursor(160, 130, 1, false);
-	}
-	::GameInterface::DrawCenteredTextScreen(origin, text, position, bg);
-}
-
 void GameInterface::DrawPlayerSprite(const DVector2& origin, bool onteam)
 {
 	int mclock = I_GetBuildTime();
diff --git a/source/games/duke/src/duke3d.h b/source/games/duke/src/duke3d.h
index aef65aea9..9b151e5e1 100644
--- a/source/games/duke/src/duke3d.h
+++ b/source/games/duke/src/duke3d.h
@@ -42,7 +42,6 @@ struct GameInterface : public ::GameInterface
 	bool CanSave() override;
 	bool StartGame(FNewGameStartup& gs) override;
 	FSavegameInfo GetSaveSig() override;
-	void DrawCenteredTextScreen(const DVector2& origin, const char* text, int position, bool bg) override;
 	double SmallFontScale() override { return isRR() ? 0.5 : 1.; }
 	void SerializeGameState(FSerializer& arc) override;
 	void QuitToTitle() override;
diff --git a/wadsrc/static/menudef.txt b/wadsrc/static/menudef.txt
index 3c6081e6d..74b481141 100644
--- a/wadsrc/static/menudef.txt
+++ b/wadsrc/static/menudef.txt
@@ -56,16 +56,15 @@ LISTMENU "MainMenu"
 	}
 	ifgame(Exhumed)
 	{
-	/*
+		class ExhumedMainMenu
 		Position 160, 65
-		centermenu
 		linespacing 22
-		NativeTextItem "3460", "n", "StartGameNoSkill", 1
-		NativeTextItem "3461", "l", "LoadGameMenu"
-		NativeTextItem "3462", "m", "StartGameNoSkill", 0
-		NativeTextItem "3463", "v", "OptionsMenu"
-		NativeTextItem "3464", "q", "QuitMenu"
-	*/
+		ExhumedPlasma
+		ExhumedTextItem "$MNU_NEWGAME", "n", "StartGameNoSkill", 1
+		ExhumedTextItem "$MNU_LOADGAME", "l", "LoadGameMenu"
+		ExhumedTextItem "$TXT_EX_MAP00", "m", "StartGameSkill", 0
+		ExhumedTextItem "$MNU_OPTIONS", "v", "OptionsMenu"
+		ExhumedTextItem "$MNU_QUITGAME", "q", "QuitMenu"
 	}
 }
 
@@ -126,19 +125,18 @@ LISTMENU "IngameMenu"
 		}
 		SWTextItem "$MNU_QUITGAME", "q", "QuitMenu"
 	}
-	/*
 	ifgame(Exhumed)
 	{
+		class ExhumedMainMenu
 		Position 160, 65
-		centermenu
 		linespacing 22
-		NativeTextItem "3460", "n", "StartGame", 1
-		NativeTextItem "3461", "l", "LoadGameMenu"
-		NativeTextItem "3462", "m", "StartGame", 0
-		NativeTextItem "3463", "v", "OptionsMenu"
-		NativeTextItem "3464", "q", "QuitMenu"
+		ExhumedLogo
+		ExhumedTextItem "$MNU_NEWGAME", "n", "StartGameNoSkill", 1
+		ExhumedTextItem "$MNU_LOADGAME", "l", "LoadGameMenu"
+		ExhumedTextItem "$TXT_EX_MAP00", "m", "StartGameSkill", 0
+		ExhumedTextItem "$MNU_OPTIONS", "v", "OptionsMenu"
+		ExhumedTextItem "$MNU_QUITGAME", "q", "QuitMenu"
 	}
-	*/
 }
 
 //-------------------------------------------------------------------------------------------
diff --git a/wadsrc/static/zscript/games/duke/ui/menu.zs b/wadsrc/static/zscript/games/duke/ui/menu.zs
index 720c57565..e90a5d3e2 100644
--- a/wadsrc/static/zscript/games/duke/ui/menu.zs
+++ b/wadsrc/static/zscript/games/duke/ui/menu.zs
@@ -25,11 +25,6 @@ class DukeMenuDelegate : RazeMenuDelegate
 		return int((y+h) * fh / 200); // This must be the covered height of the header in true pixels.
 	}
 	
-	static int calcSinTableValue(int ang)
-	{
-		return int(16384 * sin((360./2048) * ang));
-	}
-	
 	//----------------------------------------------------------------------------
 	//
 	//
@@ -43,7 +38,7 @@ class DukeMenuDelegate : RazeMenuDelegate
 		String picname;
 		if (!right) picname= String.Format("SPINNINGNUKEICON%d", ((mclock >> 3) % frames));
 		else picname = String.Format("SPINNINGNUKEICON%d", frames - 1 - ((frames - 1 + (mclock >> 3)) % frames));
-		int light = 231 + (calcSinTableValue(mclock<<5) / 768.);
+		int light = 231 + (Build.calcSinTableValue(mclock<<5) / 768.);
 		let pe = color(255, light, light, light);
 		Screen.DrawTexture(TexMan.CheckForTexture(picname), false, x, y, DTA_FullscreenScale, FSMode_Fit320x200, DTA_ScaleX, scale, DTA_ScaleY, scale, DTA_Color, pe, DTA_CenterOffsetRel, true);
 	}
@@ -90,7 +85,7 @@ class ListMenuItemDukeLogo : ListMenuItem
 			if (gameinfo.gametype & GAMEFLAG_PLUTOPAK)
 			{
 				int mclock = MSTime() * 120 / 1000;
-				int light = 223 + (DukeMenuDelegate.calcSinTableValue(mclock<<4) / 512.);
+				int light = 223 + (Build.calcSinTableValue(mclock<<4) / 512.);
 				let pe = Color(255, light, light, light);
 				Screen.DrawTexture(TexMan.CheckForTexture("MENUPLUTOPAKSPRITE"), false, x + 100, 36, DTA_FullscreenScale, FSMode_Fit320x200Top, DTA_Color, pe, DTA_CenterOffsetRel, true);
 			}
@@ -128,7 +123,7 @@ class ListMenuItemDukeTextItem : ListMenuItemTextItem
 		if (selected)
 		{
 			int mclock = MSTime() * 120 / 1000;
-			int light = 231 + (DukeMenuDelegate.calcSinTableValue(mclock<<5) / 512.);
+			int light = 231 + (Build.calcSinTableValue(mclock<<5) / 512.);
 			pe = Color(255, light, light, light);
 		}
 		else
diff --git a/wadsrc/static/zscript/games/exhumed/ui/menu.zs b/wadsrc/static/zscript/games/exhumed/ui/menu.zs
index ea8953682..18d7fafde 100644
--- a/wadsrc/static/zscript/games/exhumed/ui/menu.zs
+++ b/wadsrc/static/zscript/games/exhumed/ui/menu.zs
@@ -1,9 +1,12 @@
 
 class ExhumedMenuDelegate : RazeMenuDelegate
 {
+	double zoomsize;	// this is the only persistent place where it can be conveniently stored.
+	
 	override int DrawCaption(String title, Font fnt, int y, bool drawit)
 	{
 		let font = generic_ui? NewConsoleFont : BigFont;	// this ignores the passed font intentionally.
+		let cr = generic_ui ? Font.CR_FIRE : Font.CR_UNTRANSLATED;	// this ignores the passed font intentionally.
 		let texid = TexMan.CheckForTexture("MENUBLANK");
 		let texsize = TexMan.GetScaledSize(texid);
 		let fonth = font.GetGlyphHeight("A");
@@ -16,7 +19,7 @@ class ExhumedMenuDelegate : RazeMenuDelegate
 				if (texsize.X - 18 < width) scalex = width / (texsize.X - 18);
 				screen.DrawTexture(texid, false, 160, 20, DTA_FullscreenScale, FSMode_Fit320x200Top, DTA_CenterOffset, true, DTA_ScaleX, scalex);
 			}
-			screen.DrawText(font, Font.CR_UNTRANSLATED, 160 - width / 2, 20 - fonth / 2, title, DTA_FullscreenScale, FSMode_Fit320x200Top);
+			screen.DrawText(font, cr, 160 - width / 2, 20 - fonth / 2, title, DTA_FullscreenScale, FSMode_Fit320x200Top);
 		}
 		double fx, fy, fw, fh;
 		[fx, fy, fw, fh] = Screen.GetFullscreenRect(320, 200, FSMode_ScaleToFit43Top);
@@ -25,3 +28,103 @@ class ExhumedMenuDelegate : RazeMenuDelegate
 	}
 }
 
+//----------------------------------------------------------------------------
+//
+// 
+//
+//----------------------------------------------------------------------------
+
+class ListMenuItemExhumedPlasma : ListMenuItem
+{
+	void Init(ListMenuDescriptor desc)
+	{
+		Super.Init(0, 0);
+	}
+
+	native override void Draw(bool selected, ListMenuDescriptor desc);
+}
+
+class ListMenuItemExhumedLogo : ListMenuItem
+{
+	void Init(ListMenuDescriptor desc)
+	{
+		Super.Init(0, 0);
+	}
+
+	native override void Draw(bool selected, ListMenuDescriptor desc);
+}
+
+//----------------------------------------------------------------------------
+//
+// Menu related game interface functions
+//
+//----------------------------------------------------------------------------
+
+class ListMenuItemExhumedTextItem : ListMenuItemTextItem
+{
+	void Init(ListMenuDescriptor desc, String text, String hotkey, Name child, int param = 0)
+	{
+		Super.Init(desc, text, hotkey, child, param);
+		if (child == 'none') mEnabled = -1;
+	}
+
+	void InitDirect(double x, double y, int height, String hotkey, String text, Font font, int color, int color2, Name child, int param = 0)
+	{
+		Super.InitDirect(x, y, height, hotkey, text, font, color, color2, child, param);
+	}
+
+	override void Draw(bool selected, ListMenuDescriptor desc)
+	{
+		let font = generic_ui ? NewConsoleFont : BigFont;	// this ignores the passed font intentionally.
+		let cr = generic_ui ? Font.CR_FIRE : Font.CR_UNTRANSLATED;	// this ignores the passed font intentionally.
+		let tex = TexMan.CheckForTexture("MENUBLANK");
+		let texsize = TexMan.GetScaledSize(tex);
+		let fonth = font.GetGlyphHeight("A");
+		int width = font.StringWidth(mText);
+
+		let v = TexMan.GetScaledSize(tex);
+		double y = mYpos + v.y / 2;
+
+		int shade;
+		if (selected) shade = Build.CalcSinTableValue(MSTime() * 16 * 120 / 1000) >> 9;
+		else if (Selectable()) shade = 0;
+		else shade = 25;
+		let color = Build.shadeToLight(shade);
+
+		double scalex = 1.; // Squash the text if it is too wide. Due to design limitations we cannot expand the box here. :(
+		if (texsize.X - 18 < width) scalex = (texsize.X - 18) / width;
+
+		screen.DrawTexture(tex, false, 160, y, DTA_FullscreenScale, FSMode_Fit320x200, DTA_CenterOffset, true, DTA_ScaleX, scalex, DTA_Color, color);
+		screen.DrawText(font, cr, 160 - width / 2, y - fonth / 2, mText, DTA_FullscreenScale, FSMode_Fit320x200, DTA_Color, color);
+
+		if (selected)
+		{
+			tex = TexMan.CheckForTexture("MENUCURSORTILE");
+			screen.DrawTexture(tex, false, 62, y - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true);
+			screen.DrawTexture( tex, false, 207, y - 12, DTA_FullscreenScale, FSMode_Fit320x200, DTA_TopLeft, true, DTA_FlipX, true);
+		}
+	}
+}
+
+//----------------------------------------------------------------------------
+//
+// 
+//
+//----------------------------------------------------------------------------
+class ExhumedMainMenu : ListMenu
+{
+	override void Ticker()
+	{
+		Super.Ticker();
+		let delegate = ExhumedMenuDelegate(menuDelegate);
+		if (!delegate) return;
+		// handle the menu zoom-in
+		if (delegate.zoomsize < 1.)
+		{
+			delegate.zoomsize += 0.0625;
+			if (delegate.zoomsize >= 1.)
+				delegate.zoomsize = 1.;
+		}
+	}
+
+}
diff --git a/wadsrc/static/zscript/razebase.zs b/wadsrc/static/zscript/razebase.zs
index 4e73b7621..4f12af4c2 100644
--- a/wadsrc/static/zscript/razebase.zs
+++ b/wadsrc/static/zscript/razebase.zs
@@ -30,6 +30,11 @@ enum EGameType
 
 struct Build
 {
+	static int calcSinTableValue(int ang)
+	{
+		return int(16384 * sin((360./2048) * ang));
+	}
+	
 	native static Color shadeToLight(int shade);
 }