diff --git a/src/p_dialog.c b/src/p_dialog.c
index 6239c24e5334da90bfabe2dd1ba5a1009c9c0de3..a18fe3d24b3158d2756b3f09676b3f65e8295a43 100644
--- a/src/p_dialog.c
+++ b/src/p_dialog.c
@@ -439,9 +439,46 @@ enum
 	TP_OP_NAME,
 	TP_OP_ICON,
 
+	TP_OP_CHARNAME,
+	TP_OP_PLAYERNAME,
+
 	TP_OP_CONTROL = 0xFF
 };
 
+static boolean ProcessInstruction(dialog_t *dialog, UINT8 **cptr, UINT8 **buffer, size_t *buffer_pos, size_t *buffer_capacity)
+{
+	UINT8 *code = *cptr;
+	if (*code++ != TP_OP_CONTROL)
+		return false;
+
+	player_t *player = dialog->callplayer;
+
+	switch (*code)
+	{
+		case TP_OP_CHARNAME: {
+			char charname[256];
+			strlcpy(charname, skins[player->skin]->realname, sizeof(charname));
+			M_BufferMemWrite((UINT8 *)charname, strlen(charname), buffer, buffer_pos, buffer_capacity);
+			break;
+		}
+		case TP_OP_PLAYERNAME: {
+			char playername[256];
+			if (netgame)
+				strlcpy(playername, player_names[player-players], sizeof(playername));
+			else
+				strlcpy(playername, skins[player->skin]->realname, sizeof(playername));
+			M_BufferMemWrite((UINT8 *)playername, strlen(playername), buffer, buffer_pos, buffer_capacity);
+			break;
+		}
+		default:
+			return false;
+	}
+
+	*cptr = code;
+
+	return true;
+}
+
 static const UINT8 *InterpretBytecode(const UINT8 *code, dialog_t *dialog, textwriter_t *writer)
 {
 	int num = 0;
@@ -474,6 +511,10 @@ static const UINT8 *InterpretBytecode(const UINT8 *code, dialog_t *dialog, textw
 				Z_Free(icon);
 			}
 			break;
+		case TP_OP_CHARNAME:
+		case TP_OP_PLAYERNAME:
+			// Ignore
+			break;
 		}
 	}
 
@@ -495,6 +536,10 @@ static int SkipBytecode(const UINT8 *code)
 			UINT8 n = *code++;
 			code += n;
 			break;
+		case TP_OP_CHARNAME:
+		case TP_OP_PLAYERNAME:
+			// Nothing else to read
+			break;
 		}
 	}
 
@@ -611,13 +656,28 @@ static void P_DialogSetDisplayText(dialog_t *dialog)
 	V_WordWrapInPlace(geo.textx, geo.textr, 0, dialog->disptext);
 }
 
-static char *P_ProcessPageText(char *pagetext, size_t textlength, size_t *outlength)
+static char *P_ProcessPageText(dialog_t *dialog, UINT8 *pagetext, size_t textlength, size_t *outlength)
 {
-	*outlength = textlength;
-	char *buf = Z_Calloc(textlength + 1, PU_STATIC, NULL);
-	memcpy(buf, pagetext, textlength);
-	buf[textlength] = '\0';
-	return buf;
+	size_t buffer_pos = 0;
+	size_t buffer_capacity = 0;
+
+	UINT8 *buf = NULL;
+
+	UINT8 *text = pagetext;
+	UINT8 *end = pagetext + textlength;
+
+	while (text < end)
+	{
+		if (!ProcessInstruction(dialog, &text, &buf, &buffer_pos, &buffer_capacity))
+		{
+			M_StringBufferWrite(*text, (char**)&buf, &buffer_pos, &buffer_capacity);
+			text++;
+		}
+	}
+
+	*outlength = buffer_pos;
+
+	return (char*)buf;
 }
 
 void P_DialogSetText(dialog_t *dialog, char *pagetext, size_t textlength)
@@ -625,7 +685,7 @@ void P_DialogSetText(dialog_t *dialog, char *pagetext, size_t textlength)
 	Z_Free(dialog->pagetext);
 
 	if (pagetext && pagetext[0])
-		dialog->pagetext = P_ProcessPageText(pagetext, textlength, &dialog->pagetextlength);
+		dialog->pagetext = P_ProcessPageText(dialog, (UINT8 *)pagetext, textlength, &dialog->pagetextlength);
 	else
 	{
 		dialog->pagetextlength = 0;
@@ -2237,6 +2297,14 @@ static void InterpretControlCode(char **input, UINT8 **buf, size_t *buffer_pos,
 		else
 			EXPECTED_PARAM("ICON");
 	}
+	else if (MatchControlCode("CHARNAME", input))
+	{
+		WRITE_OP(TP_OP_CHARNAME);
+	}
+	else if (MatchControlCode("PLAYERNAME", input))
+	{
+		WRITE_OP(TP_OP_PLAYERNAME);
+	}
 }
 
 static boolean InterpretCutsceneControlCode(UINT8 chr, UINT8 **buf, size_t *buffer_pos, size_t *buffer_capacity)