tbamud/src/protocol.c
2012-03-19 03:22:28 +00:00

2516 lines
91 KiB
C

/******************************************************************************
Protocol snippet by KaVir. Released into the Public Domain in February 2011.
This snippet was originally designed to be codebase independent, but has been
modified slightly so that it runs out-of-the-box on Merc derivatives. To use
it for other codebases, just change the code in the "Diku/Merc" section below.
******************************************************************************/
/******************************************************************************
Header files.
******************************************************************************/
#include <arpa/telnet.h>
#include "protocol.h"
/******************************************************************************
The following section is for Diku/Merc derivatives. Replace as needed.
******************************************************************************/
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "screen.h"
#include "improved-edit.h"
#include "dg_scripts.h"
#include "act.h"
#include "modify.h"
static void Write( descriptor_t *apDescriptor, const char *apData )
{
if ( apDescriptor != NULL)
{
if ( apDescriptor->pProtocol->WriteOOB > 0)
{
apDescriptor->pProtocol->WriteOOB = 2;
}
}
write_to_output( apDescriptor, apData, 0 );
}
static void ReportBug( const char *apText )
{
log( "%s", apText);
}
static void InfoMessage( descriptor_t *apDescriptor, const char *apData )
{
Write( apDescriptor, "\t[F210][\toINFO\t[F210]]\tn " );
Write( apDescriptor, apData );
}
static void CompressStart( descriptor_t *apDescriptor )
{
/* If your mud uses MCCP (Mud Client Compression Protocol), you need to
* call whatever function normally starts compression from here - the
* ReportBug() call should then be deleted.
*
* Otherwise you can just ignore this function.
*/
ReportBug( "CompressStart() in protocol.c is being called, but it doesn't do anything!\n" );
}
static void CompressEnd( descriptor_t *apDescriptor )
{
/* If your mud uses MCCP (Mud Client Compression Protocol), you need to
* call whatever function normally starts compression from here - the
* ReportBug() call should then be deleted.
*
* Otherwise you can just ignore this function.
*/
ReportBug( "CompressEnd() in protocol.c is being called, but it doesn't do anything!\n" );
}
/******************************************************************************
MSDP file-scope variables.
******************************************************************************/
/* These are for the GUI_VARIABLES, my unofficial extension of MSDP. They're
* intended for clients that wish to offer a generic GUI - not as nice as a
* custom GUI, admittedly, but still better than a plain terminal window.
*
* These are per-player so that they can be customised for different characters
* (eg changing 'mana' to 'blood' for vampires). You could even allow players
* to customise the buttons and gauges themselves if you wish.
*/
static const char s_Button1[] = "\005\002Help\002help\006";
static const char s_Button2[] = "\005\002Look\002look\006";
static const char s_Button3[] = "\005\002Score\002help\006";
static const char s_Button4[] = "\005\002Equipment\002equipment\006";
static const char s_Button5[] = "\005\002Inventory\002inventory\006";
static const char s_Gauge1[] = "\005\002Health\002red\002HEALTH\002HEALTH_MAX\006";
static const char s_Gauge2[] = "\005\002Mana\002blue\002MANA\002MANA_MAX\006";
static const char s_Gauge3[] = "\005\002Movement\002green\002MOVEMENT\002MOVEMENT_MAX\006";
static const char s_Gauge4[] = "\005\002Exp TNL\002yellow\002EXPERIENCE\002EXPERIENCE_MAX\006";
static const char s_Gauge5[] = "\005\002Opponent\002darkred\002OPPONENT_HEALTH\002OPPONENT_HEALTH_MAX\006";
/******************************************************************************
MSDP variable table.
******************************************************************************/
/* Macros for readability, but you can remove them if you don't like them */
#define NUMBER_READ_ONLY false, false, false, false, -1, -1, 0, NULL
#define NUMBER_READ_ONLY_SET_TO(x) false, false, false, false, -1, -1, x, NULL
#define STRING_READ_ONLY true, false, false, false, -1, -1, 0, NULL
#define NUMBER_IN_THE_RANGE(x,y) false, true, false, false, x, y, 0, NULL
#define BOOLEAN_SET_TO(x) false, true, false, false, 0, 1, x, NULL
#define STRING_WITH_LENGTH_OF(x,y) true, true, false, false, x, y, 0, NULL
#define STRING_WRITE_ONCE(x,y) true, true, true, false, -1, -1, 0, NULL
#define STRING_GUI(x) true, false, false, true, -1, -1, 0, x
static variable_name_t VariableNameTable[eMSDP_MAX+1] =
{
/* General */
{ eMSDP_CHARACTER_NAME, "CHARACTER_NAME", STRING_READ_ONLY },
{ eMSDP_SERVER_ID, "SERVER_ID", STRING_READ_ONLY },
{ eMSDP_SERVER_TIME, "SERVER_TIME", NUMBER_READ_ONLY },
{ eMSDP_SNIPPET_VERSION, "SNIPPET_VERSION", NUMBER_READ_ONLY_SET_TO(SNIPPET_VERSION) },
/* Character */
{ eMSDP_AFFECTS, "AFFECTS", STRING_READ_ONLY },
{ eMSDP_ALIGNMENT, "ALIGNMENT", NUMBER_READ_ONLY },
{ eMSDP_EXPERIENCE, "EXPERIENCE", NUMBER_READ_ONLY },
{ eMSDP_EXPERIENCE_MAX, "EXPERIENCE_MAX", NUMBER_READ_ONLY },
{ eMSDP_EXPERIENCE_TNL, "EXPERIENCE_TNL", NUMBER_READ_ONLY },
{ eMSDP_HEALTH, "HEALTH", NUMBER_READ_ONLY },
{ eMSDP_HEALTH_MAX, "HEALTH_MAX", NUMBER_READ_ONLY },
{ eMSDP_LEVEL, "LEVEL", NUMBER_READ_ONLY },
{ eMSDP_RACE, "RACE", STRING_READ_ONLY },
{ eMSDP_CLASS, "CLASS", STRING_READ_ONLY },
{ eMSDP_MANA, "MANA", NUMBER_READ_ONLY },
{ eMSDP_MANA_MAX, "MANA_MAX", NUMBER_READ_ONLY },
{ eMSDP_WIMPY, "WIMPY", NUMBER_READ_ONLY },
{ eMSDP_PRACTICE, "PRACTICE", NUMBER_READ_ONLY },
{ eMSDP_MONEY, "MONEY", NUMBER_READ_ONLY },
{ eMSDP_MOVEMENT, "MOVEMENT", NUMBER_READ_ONLY },
{ eMSDP_MOVEMENT_MAX, "MOVEMENT_MAX", NUMBER_READ_ONLY },
{ eMSDP_HITROLL, "HITROLL", NUMBER_READ_ONLY },
{ eMSDP_DAMROLL, "DAMROLL", NUMBER_READ_ONLY },
{ eMSDP_AC, "AC", NUMBER_READ_ONLY },
{ eMSDP_STR, "STR", NUMBER_READ_ONLY },
{ eMSDP_INT, "INT", NUMBER_READ_ONLY },
{ eMSDP_WIS, "WIS", NUMBER_READ_ONLY },
{ eMSDP_DEX, "DEX", NUMBER_READ_ONLY },
{ eMSDP_CON, "CON", NUMBER_READ_ONLY },
{ eMSDP_STR_PERM, "STR_PERM", NUMBER_READ_ONLY },
{ eMSDP_INT_PERM, "INT_PERM", NUMBER_READ_ONLY },
{ eMSDP_WIS_PERM, "WIS_PERM", NUMBER_READ_ONLY },
{ eMSDP_DEX_PERM, "DEX_PERM", NUMBER_READ_ONLY },
{ eMSDP_CON_PERM, "CON_PERM", NUMBER_READ_ONLY },
/* Combat */
{ eMSDP_OPPONENT_HEALTH, "OPPONENT_HEALTH", NUMBER_READ_ONLY },
{ eMSDP_OPPONENT_HEALTH_MAX,"OPPONENT_HEALTH_MAX",NUMBER_READ_ONLY },
{ eMSDP_OPPONENT_LEVEL, "OPPONENT_LEVEL", NUMBER_READ_ONLY },
{ eMSDP_OPPONENT_NAME, "OPPONENT_NAME", STRING_READ_ONLY },
/* World */
{ eMSDP_AREA_NAME, "AREA_NAME", STRING_READ_ONLY },
{ eMSDP_ROOM_EXITS, "ROOM_EXITS", STRING_READ_ONLY },
{ eMSDP_ROOM_NAME, "ROOM_NAME", STRING_READ_ONLY },
{ eMSDP_ROOM_VNUM, "ROOM_VNUM", NUMBER_READ_ONLY },
{ eMSDP_WORLD_TIME, "WORLD_TIME", NUMBER_READ_ONLY },
/* Configurable variables */
{ eMSDP_CLIENT_ID, "CLIENT_ID", STRING_WRITE_ONCE(1,40) },
{ eMSDP_CLIENT_VERSION, "CLIENT_VERSION", STRING_WRITE_ONCE(1,40) },
{ eMSDP_PLUGIN_ID, "PLUGIN_ID", STRING_WITH_LENGTH_OF(1,40) },
{ eMSDP_ANSI_COLORS, "ANSI_COLORS", BOOLEAN_SET_TO(1) },
{ eMSDP_XTERM_256_COLORS, "XTERM_256_COLORS", BOOLEAN_SET_TO(0) },
{ eMSDP_UTF_8, "UTF_8", BOOLEAN_SET_TO(0) },
{ eMSDP_SOUND, "SOUND", BOOLEAN_SET_TO(0) },
{ eMSDP_MXP, "MXP", BOOLEAN_SET_TO(0) },
/* GUI variables */
{ eMSDP_BUTTON_1, "BUTTON_1", STRING_GUI(s_Button1) },
{ eMSDP_BUTTON_2, "BUTTON_2", STRING_GUI(s_Button2) },
{ eMSDP_BUTTON_3, "BUTTON_3", STRING_GUI(s_Button3) },
{ eMSDP_BUTTON_4, "BUTTON_4", STRING_GUI(s_Button4) },
{ eMSDP_BUTTON_5, "BUTTON_5", STRING_GUI(s_Button5) },
{ eMSDP_GAUGE_1, "GAUGE_1", STRING_GUI(s_Gauge1) },
{ eMSDP_GAUGE_2, "GAUGE_2", STRING_GUI(s_Gauge2) },
{ eMSDP_GAUGE_3, "GAUGE_3", STRING_GUI(s_Gauge3) },
{ eMSDP_GAUGE_4, "GAUGE_4", STRING_GUI(s_Gauge4) },
{ eMSDP_GAUGE_5, "GAUGE_5", STRING_GUI(s_Gauge5) },
{ eMSDP_MAX, "", 0 } /* This must always be last. */
};
/******************************************************************************
MSSP file-scope variables.
******************************************************************************/
static int s_Players = 0;
static time_t s_Uptime = 0;
/******************************************************************************
Local function prototypes.
******************************************************************************/
static void Negotiate ( descriptor_t *apDescriptor );
static void PerformHandshake ( descriptor_t *apDescriptor, char aCmd, char aProtocol );
static void PerformSubnegotiation( descriptor_t *apDescriptor, char aCmd, char *apData, int aSize );
static void ParseMSDP ( descriptor_t *apDescriptor, const char *apData );
static void ExecuteMSDPPair ( descriptor_t *apDescriptor, const char *apVariable, const char *apValue );
static void ParseATCP ( descriptor_t *apDescriptor, const char *apData );
#ifdef MUDLET_PACKAGE
static void SendATCP ( descriptor_t *apDescriptor, const char *apVariable, const char *apValue );
#endif /* MUDLET_PACKAGE */
static void SendMSSP ( descriptor_t *apDescriptor );
static char *GetMxpTag ( const char *apTag, const char *apText );
static const char *GetAnsiColour ( bool_t abBackground, int aRed, int aGreen, int aBlue );
static const char *GetRGBColour ( bool_t abBackground, int aRed, int aGreen, int aBlue );
static bool_t IsValidColour ( const char *apArgument );
static bool_t MatchString ( const char *apFirst, const char *apSecond );
static bool_t PrefixString ( const char *apPart, const char *apWhole );
static bool_t IsNumber ( const char *apString );
static char *AllocString ( const char *apString );
/******************************************************************************
ANSI colour codes.
******************************************************************************/
static const char s_Clean [] = "\033[0;00m"; /* Remove colour */
static const char s_DarkBlack [] = "\033[0;30m"; /* Black foreground */
static const char s_DarkRed [] = "\033[0;31m"; /* Red foreground */
static const char s_DarkGreen [] = "\033[0;32m"; /* Green foreground */
static const char s_DarkYellow [] = "\033[0;33m"; /* Yellow foreground */
static const char s_DarkBlue [] = "\033[0;34m"; /* Blue foreground */
static const char s_DarkMagenta [] = "\033[0;35m"; /* Magenta foreground */
static const char s_DarkCyan [] = "\033[0;36m"; /* Cyan foreground */
static const char s_DarkWhite [] = "\033[0;37m"; /* White foreground */
static const char s_BoldBlack [] = "\033[1;30m"; /* Grey foreground */
static const char s_BoldRed [] = "\033[1;31m"; /* Bright red foreground */
static const char s_BoldGreen [] = "\033[1;32m"; /* Bright green foreground */
static const char s_BoldYellow [] = "\033[1;33m"; /* Bright yellow foreground */
static const char s_BoldBlue [] = "\033[1;34m"; /* Bright blue foreground */
static const char s_BoldMagenta [] = "\033[1;35m"; /* Bright magenta foreground */
static const char s_BoldCyan [] = "\033[1;36m"; /* Bright cyan foreground */
static const char s_BoldWhite [] = "\033[1;37m"; /* Bright white foreground */
static const char s_BackBlack [] = "\033[1;40m"; /* Black background */
static const char s_BackRed [] = "\033[1;41m"; /* Red background */
static const char s_BackGreen [] = "\033[1;42m"; /* Green background */
static const char s_BackYellow [] = "\033[1;43m"; /* Yellow background */
static const char s_BackBlue [] = "\033[1;44m"; /* Blue background */
static const char s_BackMagenta [] = "\033[1;45m"; /* Magenta background */
static const char s_BackCyan [] = "\033[1;46m"; /* Cyan background */
static const char s_BackWhite [] = "\033[1;47m"; /* White background */
/******************************************************************************
Protocol global functions.
******************************************************************************/
protocol_t *ProtocolCreate( void )
{
int i; /* Loop counter */
protocol_t *pProtocol;
/* Called the first time we enter - make sure the table is correct */
static bool_t bInit = false;
if ( !bInit )
{
bInit = true;
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( VariableNameTable[i].Variable != i )
{
ReportBug( "MSDP: Variable table does not match the enums in the header.\n" );
break;
}
}
}
pProtocol = (protocol_t *) malloc(sizeof(protocol_t));
pProtocol->WriteOOB = 0;
pProtocol->bIACMode = false;
pProtocol->bNegotiated = false;
pProtocol->bBlockMXP = false;
pProtocol->bTTYPE = false;
pProtocol->bNAWS = false;
pProtocol->bCHARSET = false;
pProtocol->bMSDP = false;
pProtocol->bATCP = false;
pProtocol->bMSP = false;
pProtocol->bMXP = false;
pProtocol->bMCCP = false;
pProtocol->b256Support = eUNKNOWN;
pProtocol->ScreenWidth = 0;
pProtocol->ScreenHeight = 0;
pProtocol->pMXPVersion = AllocString("Unknown");
pProtocol->pLastTTYPE = NULL;
pProtocol->pVariables = (MSDP_t **) malloc(sizeof(MSDP_t*)*eMSDP_MAX);
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
pProtocol->pVariables[i] = (MSDP_t *) malloc(sizeof(MSDP_t));
pProtocol->pVariables[i]->bReport = false;
pProtocol->pVariables[i]->bDirty = false;
pProtocol->pVariables[i]->ValueInt = 0;
pProtocol->pVariables[i]->pValueString = NULL;
if ( VariableNameTable[i].bString )
{
if ( VariableNameTable[i].pDefault != NULL )
pProtocol->pVariables[i]->pValueString = AllocString(VariableNameTable[i].pDefault);
else if ( VariableNameTable[i].bConfigurable )
pProtocol->pVariables[i]->pValueString = AllocString("Unknown");
else /* Use an empty string */
pProtocol->pVariables[i]->pValueString = AllocString("");
}
else if ( VariableNameTable[i].Default != 0 )
{
pProtocol->pVariables[i]->ValueInt = VariableNameTable[i].Default;
}
}
return pProtocol;
}
void ProtocolDestroy( protocol_t *apProtocol )
{
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if (apProtocol->pVariables[i]->pValueString)
free(apProtocol->pVariables[i]->pValueString);
free(apProtocol->pVariables[i]);
}
free(apProtocol->pVariables);
if (apProtocol->pLastTTYPE) /* Isn't saved over copyover so may still be NULL */
free(apProtocol->pLastTTYPE);
free(apProtocol->pMXPVersion);
free(apProtocol);
}
void ProtocolInput( descriptor_t *apDescriptor, char *apData, int aSize, char *apOut )
{
static char CmdBuf[MAX_PROTOCOL_BUFFER+1];
static char IacBuf[MAX_PROTOCOL_BUFFER+1];
int CmdIndex = 0;
int IacIndex = 0;
int Index;
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
for ( Index = 0; Index < aSize; ++Index )
{
/* If we'd overflow the buffer, we just ignore the input */
if ( CmdIndex >= MAX_PROTOCOL_BUFFER || IacIndex >= MAX_PROTOCOL_BUFFER )
{
ReportBug("ProtocolInput: Too much incoming data to store in the buffer.\n");
return;
}
/* IAC IAC is treated as a single value of 255 */
if ( apData[Index] == (char)IAC && apData[Index+1] == (char)IAC )
{
if ( pProtocol->bIACMode )
IacBuf[IacIndex++] = (char)IAC;
else /* In-band command */
CmdBuf[CmdIndex++] = (char)IAC;
Index++;
}
else if ( pProtocol->bIACMode )
{
/* End subnegotiation. */
if ( apData[Index] == (char)IAC && apData[Index+1] == (char)SE )
{
Index++;
pProtocol->bIACMode = false;
IacBuf[IacIndex] = '\0';
if ( IacIndex >= 2 )
PerformSubnegotiation( apDescriptor, IacBuf[0], &IacBuf[1], IacIndex-1 );
IacIndex = 0;
}
else
IacBuf[IacIndex++] = apData[Index];
}
else if ( apData[Index] == (char)27 && apData[Index+1] == '[' &&
isdigit(apData[Index+2]) && apData[Index+3] == 'z' )
{
char MXPBuffer [1024];
char *pMXPTag = NULL;
int i = 0; /* Loop counter */
Index += 4; /* Skip to the start of the MXP sequence. */
while ( Index < aSize && apData[Index] != '>' && i < 1000 )
{
MXPBuffer[i++] = apData[Index++];
}
MXPBuffer[i++] = '>';
MXPBuffer[i] = '\0';
if ( ( pMXPTag = GetMxpTag( "CLIENT=", MXPBuffer ) ) != NULL )
{
/* Overwrite the previous client name - this is harder to fake */
free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pMXPTag);
}
if ( ( pMXPTag = GetMxpTag( "VERSION=", MXPBuffer ) ) != NULL )
{
const char *pClientName = pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString;
InfoMessage(apDescriptor, "Receiving MXP Version From Client.\r\n");
free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pMXPTag);
if ( MatchString( "MUSHCLIENT", pClientName ) )
{
/* MUSHclient 4.02 and later supports 256 colours. */
if ( strcmp(pMXPTag, "4.02") >= 0 )
{
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
else /* We know for sure that 256 colours are not supported */
pProtocol->b256Support = eNO;
}
else if ( MatchString( "CMUD", pClientName ) )
{
/* CMUD 3.04 and later supports 256 colours. */
if ( strcmp(pMXPTag, "3.04") >= 0 )
{
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
else /* We know for sure that 256 colours are not supported */
pProtocol->b256Support = eNO;
}
else if ( MatchString( "ATLANTIS", pClientName ) )
{
/* Atlantis 0.9.9.0 supports XTerm 256 colours, but it doesn't
* yet have MXP. However MXP is planned, so once it responds
* to a <VERSION> tag we'll know we can use 256 colours.
*/
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
}
if ( ( pMXPTag = GetMxpTag( "MXP=", MXPBuffer ) ) != NULL )
{
free(pProtocol->pMXPVersion);
pProtocol->pMXPVersion = AllocString(pMXPTag);
}
/* No longer necessary
*
if ( strcmp(pProtocol->pMXPVersion, "Unknown") )
{
Write( apDescriptor, "\n" );
sprintf( MXPBuffer, "MXP version %s detected and enabled.\r\n",
pProtocol->pMXPVersion );
InfoMessage( apDescriptor, MXPBuffer );
} */
}
else /* In-band command */
{
if ( apData[Index] == (char)IAC )
{
switch ( apData[Index+1] )
{
case (char)SB: /* Begin subnegotiation. */
Index++;
pProtocol->bIACMode = true;
break;
case (char)DO: /* Handshake. */
case (char)DONT:
case (char)WILL:
case (char)WONT:
PerformHandshake( apDescriptor, apData[Index+1], apData[Index+2] );
Index += 2;
break;
case (char)IAC: /* Two IACs count as one. */
CmdBuf[CmdIndex++] = (char)IAC;
Index++;
break;
default: /* Skip it. */
Index++;
break;
}
}
else
CmdBuf[CmdIndex++] = apData[Index];
}
}
/* Terminate the two buffers */
IacBuf[IacIndex] = '\0';
CmdBuf[CmdIndex] = '\0';
/* Copy the input buffer back to the player. */
strcat( apOut, CmdBuf );
}
const char *ProtocolOutput( descriptor_t *apDescriptor, const char *apData, int *apLength )
{
static char Result[MAX_OUTPUT_BUFFER+1];
const char Tab[] = "\t";
const char MSP[] = "!!";
const char MXPStart[] = "\033[1z<";
const char MXPStop[] = ">\033[7z";
const char LinkStart[] = "\033[1z<send>\033[7z";
const char LinkStop[] = "\033[1z</send>\033[7z";
bool_t bTerminate = false, bUseMXP = false, bUseMSP = false;
int i = 0, j = 0; /* Index values */
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol == NULL || apData == NULL )
return apData;
/* Strip !!SOUND() triggers if they support MSP or are using sound */
if ( pProtocol->bMSP || pProtocol->pVariables[eMSDP_SOUND]->ValueInt )
bUseMSP = true;
for ( ; i < MAX_OUTPUT_BUFFER && apData[j] != '\0' && !bTerminate &&
(*apLength <= 0 || j < *apLength); ++j )
{
if ( apData[j] == '\t' )
{
const char *pCopyFrom = NULL;
switch ( apData[++j] )
{
case '\t': /* Two tabs in a row will display an actual tab */
pCopyFrom = Tab;
break;
case '_':
pCopyFrom = "\x1B[4m"; /* Underline */
break;
case '+':
pCopyFrom = "\x1B[1m"; /* Bold */
break;
case '-':
pCopyFrom = "\x1B[5m"; /* Blinking??? */
break;
case '*':
pCopyFrom = "@"; /* The At Symbol... I don't really like this, but it seems like
a simple way to allow for the @ symbol while maintain portability
between pre-ProtocolOutput() muds and post ProtocolOutput() muds.*/
break;
/* 1,2,3 to be used a MUD's base colour palette. Just to maintain
* some sort of common colouring scheme amongst coders/builders */
case '1':
pCopyFrom = ColourRGB(apDescriptor, "F022");
break;
case '2':
pCopyFrom = ColourRGB(apDescriptor, "F055");
break;
case '3':
pCopyFrom = ColourRGB(apDescriptor, "F555");
break;
case 'n':
pCopyFrom = s_Clean;
break;
case 'd': /* dark grey / black */
pCopyFrom = ColourRGB(apDescriptor, "F000");
break;
case 'D': /* light grey */
pCopyFrom = ColourRGB(apDescriptor, "F111");
break;
case 'a': /* dark azure */
pCopyFrom = ColourRGB(apDescriptor, "F021");
break;
case 'A': /* light Azure */
pCopyFrom = ColourRGB(apDescriptor, "F053");
break;
case 'r': /* dark red */
pCopyFrom = ColourRGB(apDescriptor, "F200");
break;
case 'R': /* light red */
pCopyFrom = ColourRGB(apDescriptor, "F500");
break;
case 'g': /* dark green */
pCopyFrom = ColourRGB(apDescriptor, "F020");
break;
case 'G': /* light green */
pCopyFrom = ColourRGB(apDescriptor, "F050");
break;
case 'y': /* dark yellow */
pCopyFrom = ColourRGB(apDescriptor, "F330");
break;
case 'Y': /* light yellow */
pCopyFrom = ColourRGB(apDescriptor, "F550");
break;
case 'b': /* dark blue */
pCopyFrom = ColourRGB(apDescriptor, "F012");
break;
case 'B': /* light blue */
pCopyFrom = ColourRGB(apDescriptor, "F025");
break;
case 'm': /* dark magenta */
pCopyFrom = ColourRGB(apDescriptor, "F202");
break;
case 'M': /* light magenta */
pCopyFrom = ColourRGB(apDescriptor, "F505");
break;
case 'c': /* dark cyan */
pCopyFrom = ColourRGB(apDescriptor, "F022");
break;
case 'C': /* light cyan */
pCopyFrom = ColourRGB(apDescriptor, "F055");
break;
case 'w': /* dark white */
pCopyFrom = ColourRGB(apDescriptor, "F333");
break;
case 'W': /* light white */
pCopyFrom = ColourRGB(apDescriptor, "F555");
break;
case 'o': /* dark orange */
pCopyFrom = ColourRGB(apDescriptor, "F520");
break;
case 'O': /* light orange */
pCopyFrom = ColourRGB(apDescriptor, "F530");
break;
case 'p': /* dark pink */
pCopyFrom = ColourRGB(apDescriptor, "F301");
break;
case 'P': /* light pink */
pCopyFrom = ColourRGB(apDescriptor, "F501");
break;
case '(': /* MXP link */
if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
pCopyFrom = LinkStart;
break;
case ')': /* MXP link */
if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
pCopyFrom = LinkStop;
pProtocol->bBlockMXP = false;
break;
case '<':
if ( !pProtocol->bBlockMXP && pProtocol->pVariables[eMSDP_MXP]->ValueInt )
{
pCopyFrom = MXPStart;
bUseMXP = true;
}
else /* No MXP support, so just strip it out */
{
while ( apData[j] != '\0' && apData[j] != '>' )
++j;
}
pProtocol->bBlockMXP = false;
break;
case '[':
if ( tolower(apData[++j]) == 'u' )
{
char Buffer[8] = {'\0'}, BugString[256];
int Index = 0;
int Number = 0;
bool_t bDone = false, bValid = true;
while ( isdigit(apData[++j]) )
{
Number *= 10;
Number += (apData[j])-'0';
}
if ( apData[j] == '/' )
++j;
while ( apData[j] != '\0' && !bDone )
{
if ( apData[j] == ']' )
bDone = true;
else if ( Index < 7 )
Buffer[Index++] = apData[j++];
else /* It's too long, so ignore the rest and note the problem */
{
j++;
bValid = false;
}
}
if ( !bDone )
{
sprintf( BugString, "BUG: Unicode substitute '%s' wasn't terminated with ']'.\n", Buffer );
ReportBug( BugString );
}
else if ( !bValid )
{
sprintf( BugString, "BUG: Unicode substitute '%s' truncated. Missing ']'?\n", Buffer );
ReportBug( BugString );
}
else if ( pProtocol->pVariables[eMSDP_UTF_8]->ValueInt )
{
pCopyFrom = UnicodeGet(Number);
}
else /* Display the substitute string */
{
pCopyFrom = Buffer;
}
/* Terminate if we've reached the end of the string */
bTerminate = !bDone;
}
else if ( tolower(apData[j]) == 'f' || tolower(apData[j]) == 'b' )
{
char Buffer[8] = {'\0'}, BugString[256];
int Index = 0;
bool_t bDone = false, bValid = true;
/* Copy the 'f' (foreground) or 'b' (background) */
Buffer[Index++] = apData[j++];
while ( apData[j] != '\0' && !bDone && bValid )
{
if ( apData[j] == ']' )
bDone = true;
else if ( Index < 4 )
Buffer[Index++] = apData[j++];
else /* It's too long, so drop out - the colour code may still be valid */
bValid = false;
}
if ( !bDone || !bValid)
{
sprintf( BugString, "BUG: RGB %sground colour '%s' wasn't terminated with ']'.\n",
(tolower(Buffer[0]) == 'f') ? "fore" : "back", &Buffer[1] );
ReportBug( BugString );
}
else if ( !IsValidColour(Buffer) )
{
sprintf( BugString, "BUG: RGB %sground colour '%s' invalid (each digit must be in the range 0-5).\n",
(tolower(Buffer[0]) == 'f') ? "fore" : "back", &Buffer[1] );
ReportBug( BugString );
}
else /* Success */
{
pCopyFrom = ColourRGB(apDescriptor, Buffer);
}
}
else if ( tolower(apData[j]) == 'x' )
{
char Buffer[8] = {'\0'}, BugString[256];
int Index = 0;
bool_t bDone = false, bValid = true;
++j; /* Skip the 'x' */
while ( apData[j] != '\0' && !bDone )
{
if ( apData[j] == ']' )
bDone = true;
else if ( Index < 7 )
Buffer[Index++] = apData[j++];
else /* It's too long, so ignore the rest and note the problem */
{
j++;
bValid = false;
}
}
if ( !bDone )
{
sprintf( BugString, "BUG: Required MXP version '%s' wasn't terminated with ']'.\n", Buffer );
ReportBug( BugString );
}
else if ( !bValid )
{
sprintf( BugString, "BUG: Required MXP version '%s' too long. Missing ']'?\n", Buffer );
ReportBug( BugString );
}
else if ( !strcmp(pProtocol->pMXPVersion, "Unknown") ||
strcmp(pProtocol->pMXPVersion, Buffer) < 0 )
{
/* Their version of MXP isn't high enough */
pProtocol->bBlockMXP = true;
}
else /* MXP is sufficient for this tag */
{
pProtocol->bBlockMXP = false;
}
/* Terminate if we've reached the end of the string */
bTerminate = !bDone;
}
break;
case '!': /* Used for in-band MSP sound triggers */
pCopyFrom = MSP;
break;
case '\0':
bTerminate = true;
break;
default:
break;
}
/* Copy the colour code, if any. */
if ( pCopyFrom != NULL )
{
while ( *pCopyFrom != '\0' && i < MAX_OUTPUT_BUFFER )
Result[i++] = *pCopyFrom++;
}
}
else if ( bUseMXP && apData[j] == '>' )
{
const char *pCopyFrom = MXPStop;
while ( *pCopyFrom != '\0' && i < MAX_OUTPUT_BUFFER)
Result[i++] = *pCopyFrom++;
bUseMXP = false;
}
else if ( bUseMSP && j > 0 && apData[j-1] == '!' && apData[j] == '!' &&
PrefixString("SOUND(", &apData[j+1]) )
{
/* Avoid accidental triggering of old-style MSP triggers */
Result[i++] = '?';
}
else /* Just copy the character normally */
{
Result[i++] = apData[j];
}
}
/* If we'd overflow the buffer, we don't send any output */
if ( i >= MAX_OUTPUT_BUFFER )
{
i = 0;
ReportBug("ProtocolOutput: Too much outgoing data to store in the buffer.\n");
}
/* Terminate the string */
Result[i] = '\0';
/* Store the length */
if ( apLength )
*apLength = i;
/* Return the string */
return Result;
}
/* Some clients (such as GMud) don't properly handle negotiation, and simply
* display every printable character to the screen. However TTYPE isn't a
* printable character, so we negotiate for it first, and only negotiate for
* other protocols if the client responds with IAC WILL TTYPE or IAC WONT
* TTYPE. Thanks go to Donky on MudBytes for the suggestion.
*/
void ProtocolNegotiate( descriptor_t *apDescriptor )
{
static const char DoTTYPE [] = { (char)IAC, (char)DO, TELOPT_TTYPE, '\0' };
Write(apDescriptor, DoTTYPE);
}
/******************************************************************************
Copyover save/load functions.
******************************************************************************/
const char *CopyoverGet( descriptor_t *apDescriptor )
{
static char Buffer[64];
char *pBuffer = Buffer;
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL )
{
sprintf(Buffer, "%d/%d", pProtocol->ScreenWidth, pProtocol->ScreenHeight);
/* Skip to the end */
while ( *pBuffer != '\0' )
++pBuffer;
if ( pProtocol->bTTYPE )
*pBuffer++ = 'T';
if ( pProtocol->bNAWS )
*pBuffer++ = 'N';
if ( pProtocol->bMSDP )
*pBuffer++ = 'M';
if ( pProtocol->bATCP )
*pBuffer++ = 'A';
if ( pProtocol->bMSP )
*pBuffer++ = 'S';
if ( pProtocol->pVariables[eMSDP_MXP]->ValueInt )
*pBuffer++ = 'X';
if ( pProtocol->bMCCP )
{
*pBuffer++ = 'c';
CompressEnd(apDescriptor);
}
if ( pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt )
*pBuffer++ = 'C';
if ( pProtocol->bCHARSET )
*pBuffer++ = 'H';
if ( pProtocol->pVariables[eMSDP_UTF_8]->ValueInt )
*pBuffer++ = 'U';
}
/* Terminate the string */
*pBuffer = '\0';
return Buffer;
}
void CopyoverSet( descriptor_t *apDescriptor, const char *apData )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && apData != NULL )
{
int Width = 0, Height = 0;
bool_t bDoneWidth = false;
int i; /* Loop counter */
for ( i = 0; apData[i] != '\0'; ++i )
{
switch ( apData[i] )
{
case 'T':
pProtocol->bTTYPE = true;
break;
case 'N':
pProtocol->bNAWS = true;
break;
case 'M':
pProtocol->bMSDP = true;
break;
case 'A':
pProtocol->bATCP = true;
break;
case 'S':
pProtocol->bMSP = true;
break;
case 'X':
pProtocol->bMXP = true;
pProtocol->pVariables[eMSDP_MXP]->ValueInt = 1;
break;
case 'c':
pProtocol->bMCCP = true;
CompressStart(apDescriptor);
break;
case 'C':
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
break;
case 'H':
pProtocol->bCHARSET = true;
break;
case 'U':
pProtocol->pVariables[eMSDP_UTF_8]->ValueInt = 1;
break;
default:
if ( apData[i] == '/' )
bDoneWidth = true;
else if ( isdigit(apData[i]) )
{
if ( bDoneWidth )
{
Height *= 10;
Height += (apData[i] - '0');
}
else /* We're still calculating height */
{
Width *= 10;
Width += (apData[i] - '0');
}
}
break;
}
}
/* Restore the width and height */
pProtocol->ScreenWidth = Width;
pProtocol->ScreenHeight = Height;
/* If we're using MSDP or ATCP, we need to renegotiate it so that the
* client can resend the list of variables it wants us to REPORT.
*
* Note that we only use ATCP if MSDP is not supported.
*/
if ( pProtocol->bMSDP )
{
char WillMSDP [] = { (char)IAC, (char)WILL, TELOPT_MSDP, '\0' };
Write(apDescriptor, WillMSDP);
}
else if ( pProtocol->bATCP )
{
char DoATCP [] = { (char)IAC, (char)DO, (char)TELOPT_ATCP, '\0' };
Write(apDescriptor, DoATCP);
}
/* Ask the client to send its MXP version again */
if ( pProtocol->bMXP )
MXPSendTag( apDescriptor, "<VERSION>" );
}
}
/******************************************************************************
MSDP global functions.
******************************************************************************/
void MSDPUpdate( descriptor_t *apDescriptor )
{
int i; /* Loop counter */
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( pProtocol->pVariables[i]->bReport )
{
if ( pProtocol->pVariables[i]->bDirty )
{
MSDPSend( apDescriptor, (variable_t)i );
pProtocol->pVariables[i]->bDirty = false;
}
}
}
}
void MSDPFlush( descriptor_t *apDescriptor, variable_t aMSDP )
{
if ( aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol->pVariables[aMSDP]->bReport )
{
if ( pProtocol->pVariables[aMSDP]->bDirty )
{
MSDPSend( apDescriptor, aMSDP );
pProtocol->pVariables[aMSDP]->bDirty = false;
}
}
}
}
void MSDPSend( descriptor_t *apDescriptor, variable_t aMSDP )
{
char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };
if ( aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( VariableNameTable[aMSDP].bString )
{
/* Should really be replaced with a dynamic buffer */
int RequiredBuffer = strlen(VariableNameTable[aMSDP].pName) +
strlen(pProtocol->pVariables[aMSDP]->pValueString) + 12;
if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
{
sprintf( MSDPBuffer,
"MSDPSend: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
VariableNameTable[aMSDP].pName, RequiredBuffer,
MAX_VARIABLE_LENGTH );
ReportBug( MSDPBuffer );
MSDPBuffer[0] = '\0';
}
else if ( pProtocol->bMSDP )
{
sprintf( MSDPBuffer, "%c%c%c%c%s%c%s%c%c",
IAC, SB, TELOPT_MSDP, MSDP_VAR,
VariableNameTable[aMSDP].pName, MSDP_VAL,
pProtocol->pVariables[aMSDP]->pValueString, IAC, SE );
}
else if ( pProtocol->bATCP )
{
sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c",
IAC, SB, TELOPT_ATCP,
VariableNameTable[aMSDP].pName,
pProtocol->pVariables[aMSDP]->pValueString, IAC, SE );
}
}
else /* It's an integer, not a string */
{
if ( pProtocol->bMSDP )
{
sprintf( MSDPBuffer, "%c%c%c%c%s%c%d%c%c",
IAC, SB, TELOPT_MSDP, MSDP_VAR,
VariableNameTable[aMSDP].pName, MSDP_VAL,
pProtocol->pVariables[aMSDP]->ValueInt, IAC, SE );
}
else if ( pProtocol->bATCP )
{
sprintf( MSDPBuffer, "%c%c%cMSDP.%s %d%c%c",
IAC, SB, TELOPT_ATCP,
VariableNameTable[aMSDP].pName,
pProtocol->pVariables[aMSDP]->ValueInt, IAC, SE );
}
}
/* Just in case someone calls this function without checking MSDP/ATCP */
if ( MSDPBuffer[0] != '\0' )
Write( apDescriptor, MSDPBuffer );
}
}
void MSDPSendPair( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };
if ( apVariable != NULL && apValue != NULL )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
/* Should really be replaced with a dynamic buffer */
int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;
if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
{
if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
{
sprintf( MSDPBuffer,
"MSDPSendPair: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
}
else /* The variable name itself is too long */
{
sprintf( MSDPBuffer,
"MSDPSendPair: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
RequiredBuffer, MAX_VARIABLE_LENGTH );
}
ReportBug( MSDPBuffer );
MSDPBuffer[0] = '\0';
}
else if ( pProtocol->bMSDP )
{
sprintf( MSDPBuffer, "%c%c%c%c%s%c%s%c%c",
IAC, SB, TELOPT_MSDP, MSDP_VAR, apVariable, MSDP_VAL,
apValue, IAC, SE );
}
else if ( pProtocol->bATCP )
{
sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c",
IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
}
/* Just in case someone calls this function without checking MSDP/ATCP */
if ( MSDPBuffer[0] != '\0' )
Write( apDescriptor, MSDPBuffer );
}
}
void MSDPSendList( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
char MSDPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };
if ( apVariable != NULL && apValue != NULL )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
/* Should really be replaced with a dynamic buffer */
int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;
if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
{
if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
{
sprintf( MSDPBuffer,
"MSDPSendList: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
}
else /* The variable name itself is too long */
{
sprintf( MSDPBuffer,
"MSDPSendList: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
RequiredBuffer, MAX_VARIABLE_LENGTH );
}
ReportBug( MSDPBuffer );
MSDPBuffer[0] = '\0';
}
else if ( pProtocol->bMSDP )
{
int i; /* Loop counter */
sprintf( MSDPBuffer, "%c%c%c%c%s%c%c%c%s%c%c%c",
IAC, SB, TELOPT_MSDP, MSDP_VAR, apVariable, MSDP_VAL,
MSDP_ARRAY_OPEN, MSDP_VAL, apValue, MSDP_ARRAY_CLOSE, IAC, SE );
/* Convert the spaces to MSDP_VAL */
for ( i = 0; MSDPBuffer[i] != '\0'; ++i )
{
if ( MSDPBuffer[i] == ' ' )
MSDPBuffer[i] = MSDP_VAL;
}
}
else if ( pProtocol->bATCP )
{
sprintf( MSDPBuffer, "%c%c%cMSDP.%s %s%c%c",
IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
}
/* Just in case someone calls this function without checking MSDP/ATCP */
if ( MSDPBuffer[0] != '\0' )
Write( apDescriptor, MSDPBuffer );
}
}
void MSDPSetNumber( descriptor_t *apDescriptor, variable_t aMSDP, int aValue )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && aMSDP > eMSDP_NONE && aMSDP < eMSDP_MAX )
{
if ( !VariableNameTable[aMSDP].bString )
{
if ( pProtocol->pVariables[aMSDP]->ValueInt != aValue )
{
pProtocol->pVariables[aMSDP]->ValueInt = aValue;
pProtocol->pVariables[aMSDP]->bDirty = true;
}
}
}
}
void MSDPSetString( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && apValue != NULL )
{
if ( VariableNameTable[aMSDP].bString )
{
if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, apValue) )
{
free(pProtocol->pVariables[aMSDP]->pValueString);
pProtocol->pVariables[aMSDP]->pValueString = AllocString(apValue);
pProtocol->pVariables[aMSDP]->bDirty = true;
}
}
}
}
void MSDPSetTable( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && apValue != NULL )
{
if ( *apValue == '\0' )
{
/* It's easier to call MSDPSetString if the value is empty */
MSDPSetString(apDescriptor, aMSDP, apValue);
}
else if ( VariableNameTable[aMSDP].bString )
{
const char MsdpTableStart[] = { (char)MSDP_TABLE_OPEN, '\0' };
const char MsdpTableStop[] = { (char)MSDP_TABLE_CLOSE, '\0' };
char *pTable = (char *) malloc(strlen(apValue) + 3); /* 3: START, STOP, NUL */
strcpy(pTable, MsdpTableStart);
strcat(pTable, apValue);
strcat(pTable, MsdpTableStop);
if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, pTable) )
{
free(pProtocol->pVariables[aMSDP]->pValueString);
pProtocol->pVariables[aMSDP]->pValueString = pTable;
pProtocol->pVariables[aMSDP]->bDirty = true;
}
else /* Just discard the table, we've already got one */
{
free(pTable);
}
}
}
}
void MSDPSetArray( descriptor_t *apDescriptor, variable_t aMSDP, const char *apValue )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && apValue != NULL )
{
if ( *apValue == '\0' )
{
/* It's easier to call MSDPSetString if the value is empty */
MSDPSetString(apDescriptor, aMSDP, apValue);
}
else if ( VariableNameTable[aMSDP].bString )
{
const char MsdpArrayStart[] = { (char)MSDP_ARRAY_OPEN, '\0' };
const char MsdpArrayStop[] = { (char)MSDP_ARRAY_CLOSE, '\0' };
char *pArray = (char *) malloc(strlen(apValue) + 3); /* 3: START, STOP, NUL */
strcpy(pArray, MsdpArrayStart);
strcat(pArray, apValue);
strcat(pArray, MsdpArrayStop);
if ( strcmp(pProtocol->pVariables[aMSDP]->pValueString, pArray) )
{
free(pProtocol->pVariables[aMSDP]->pValueString);
pProtocol->pVariables[aMSDP]->pValueString = pArray;
pProtocol->pVariables[aMSDP]->bDirty = true;
}
else /* Just discard the array, we've already got one */
{
free(pArray);
}
}
}
}
/******************************************************************************
MSSP global functions.
******************************************************************************/
void MSSPSetPlayers( int aPlayers )
{
s_Players = aPlayers;
if ( s_Uptime == 0 )
s_Uptime = time(0);
}
/******************************************************************************
MXP global functions.
******************************************************************************/
const char *MXPCreateTag( descriptor_t *apDescriptor, const char *apTag )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && pProtocol->pVariables[eMSDP_MXP]->ValueInt &&
strlen(apTag) < 1000 )
{
static char MXPBuffer [1024];
sprintf( MXPBuffer, "\033[1z%s\033[7z", apTag );
return MXPBuffer;
}
else /* Leave the tag as-is, don't try to MXPify it */
{
return apTag;
}
}
void MXPSendTag( descriptor_t *apDescriptor, const char *apTag )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && pProtocol->pVariables[eMSDP_MXP]->ValueInt &&
strlen(apTag) < 1000 )
{
char MXPBuffer [1024];
sprintf(MXPBuffer, "\033[1z%s\033[7z\r\n", apTag );
Write(apDescriptor, MXPBuffer);
}
}
/******************************************************************************
Sound global functions.
******************************************************************************/
void SoundSend( descriptor_t *apDescriptor, const char *apTrigger )
{
const int MaxTriggerLength = 128; /* Used for the buffer size */
if ( apDescriptor != NULL && apTrigger != NULL )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
if ( pProtocol != NULL && pProtocol->pVariables[eMSDP_SOUND]->ValueInt )
{
if ( pProtocol->bMSDP || pProtocol->bATCP )
{
/* Send the sound trigger through MSDP or ATCP */
MSDPSendPair( apDescriptor, "PLAY_SOUND", apTrigger );
}
else if ( strlen(apTrigger) <= MaxTriggerLength )
{
/* Use an old MSP-style trigger */
char *pBuffer = alloca(MaxTriggerLength+10);
sprintf( pBuffer, "\t!SOUND(%s)", apTrigger );
Write(apDescriptor, pBuffer);
}
}
}
}
/******************************************************************************
Colour global functions.
******************************************************************************/
const char *ColourRGB( descriptor_t *apDescriptor, const char *apRGB )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
bool charHasColor = TRUE;
if (apDescriptor->character && !clr(apDescriptor->character, C_CMP))
charHasColor = FALSE;
if ( pProtocol && pProtocol->pVariables[eMSDP_ANSI_COLORS]->ValueInt && charHasColor )
{
if ( IsValidColour(apRGB) )
{
bool_t bBackground = (tolower(apRGB[0]) == 'b');
int Red = apRGB[1] - '0';
int Green = apRGB[2] - '0';
int Blue = apRGB[3] - '0';
if ( pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt )
return GetRGBColour( bBackground, Red, Green, Blue );
else /* Use regular ANSI colour */
return GetAnsiColour( bBackground, Red, Green, Blue );
}
else /* Invalid colour - use this to clear any existing colour. */
{
return s_Clean;
}
}
else /* Don't send any colour, not even clear */
{
return "";
}
}
/******************************************************************************
UTF-8 global functions.
******************************************************************************/
char *UnicodeGet( int aValue )
{
static char Buffer[8];
char *pString = Buffer;
UnicodeAdd( &pString, aValue );
*pString = '\0';
return Buffer;
}
void UnicodeAdd( char **apString, int aValue )
{
if ( aValue < 0x80 )
{
*(*apString)++ = (char)aValue;
}
else if ( aValue < 0x800 )
{
*(*apString)++ = (char)(0xC0 | (aValue>>6));
*(*apString)++ = (char)(0x80 | (aValue & 0x3F));
}
else if ( aValue < 0x10000 )
{
*(*apString)++ = (char)(0xE0 | (aValue>>12));
*(*apString)++ = (char)(0x80 | (aValue>>6 & 0x3F));
*(*apString)++ = (char)(0x80 | (aValue & 0x3F));
}
else if ( aValue < 0x200000 )
{
*(*apString)++ = (char)(0xF0 | (aValue>>18));
*(*apString)++ = (char)(0x80 | (aValue>>12 & 0x3F));
*(*apString)++ = (char)(0x80 | (aValue>>6 & 0x3F));
*(*apString)++ = (char)(0x80 | (aValue & 0x3F));
}
}
/******************************************************************************
Local negotiation functions.
******************************************************************************/
static void Negotiate( descriptor_t *apDescriptor )
{
protocol_t *pProtocol = apDescriptor->pProtocol;
if ( pProtocol->bNegotiated )
{
const char RequestTTYPE [] = { (char)IAC, (char)SB, TELOPT_TTYPE, SEND, (char)IAC, (char)SE, '\0' };
const char DoNAWS [] = { (char)IAC, (char)DO, TELOPT_NAWS, '\0' };
const char DoCHARSET [] = { (char)IAC, (char)DO, TELOPT_CHARSET, '\0' };
const char WillMSDP [] = { (char)IAC, (char)WILL, TELOPT_MSDP, '\0' };
const char WillMSSP [] = { (char)IAC, (char)WILL, TELOPT_MSSP, '\0' };
const char DoATCP [] = { (char)IAC, (char)DO, (char)TELOPT_ATCP,'\0' };
const char WillMSP [] = { (char)IAC, (char)WILL, TELOPT_MSP, '\0' };
const char DoMXP [] = { (char)IAC, (char)DO, TELOPT_MXP, '\0' };
#ifdef USING_MCCP
const char WillMCCP [] = { (char)IAC, (char)WILL, TELOPT_MCCP, '\0' };
#endif // USING_MCCP
/* Request the client type if TTYPE is supported. */
if ( pProtocol->bTTYPE )
Write(apDescriptor, RequestTTYPE);
/* Check for other protocols. */
Write(apDescriptor, DoNAWS);
Write(apDescriptor, DoCHARSET);
Write(apDescriptor, WillMSDP);
Write(apDescriptor, WillMSSP);
Write(apDescriptor, DoATCP);
Write(apDescriptor, WillMSP);
Write(apDescriptor, DoMXP);
#ifdef USING_MCCP
Write(apDescriptor, WillMCCP);
#endif // USING_MCCP
}
}
static void PerformHandshake( descriptor_t *apDescriptor, char aCmd, char aProtocol )
{
bool_t bResult = true;
protocol_t *pProtocol = apDescriptor->pProtocol;
switch ( aProtocol )
{
case (char)TELOPT_TTYPE:
if ( aCmd == (char)WILL )
{
if ( !pProtocol->bNegotiated )
{
/* Negotiate for the remaining protocols. */
pProtocol->bNegotiated = true;
pProtocol->bTTYPE = true;
Negotiate(apDescriptor);
}
}
else if ( aCmd == (char)WONT )
{
if ( !pProtocol->bNegotiated )
{
/* Still negotiate, as this client obviously knows how to
* correctly respond to negotiation attempts - but we don't
* ask for TTYPE, as it doesn't support it.
*/
pProtocol->bNegotiated = true;
pProtocol->bTTYPE = false;
Negotiate(apDescriptor);
}
}
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_NAWS:
if ( aCmd == (char)WILL )
pProtocol->bNAWS = true;
else if ( aCmd == (char)WONT )
pProtocol->bNAWS = false;
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_CHARSET:
if ( aCmd == (char)WILL )
{
char charset_utf8 [] = { (char)IAC, (char)SB, TELOPT_CHARSET, 1, ' ', 'U', 'T', 'F', '-', '8', (char)IAC, (char)SE, '\0' };
Write(apDescriptor, charset_utf8);
pProtocol->bCHARSET = true;
}
else if ( aCmd == (char)WONT )
pProtocol->bCHARSET = false;
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_MSDP:
if ( aCmd == (char)DO )
{
pProtocol->bMSDP = true;
/* Identify the mud to the client. */
MSDPSendPair( apDescriptor, "SERVER_ID", MUD_NAME );
}
else if ( aCmd == (char)DONT )
pProtocol->bMSDP = false;
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_MSSP:
if ( aCmd == (char)DO )
SendMSSP( apDescriptor );
else if ( aCmd == (char)DONT )
; /* Do nothing. */
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_MCCP:
if ( aCmd == (char)DO )
{
pProtocol->bMCCP = true;
CompressStart( apDescriptor );
}
else if ( aCmd == (char)DONT )
{
pProtocol->bMCCP = false;
CompressEnd( apDescriptor );
}
else // Anything else is invalid.
bResult = false;
break;
case (char)TELOPT_MSP:
if ( aCmd == (char)DO )
pProtocol->bMSP = true;
else if ( aCmd == (char)DONT )
pProtocol->bMSP = false;
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_MXP:
if ( aCmd == (char)WILL || aCmd == (char)DO )
{
/* Enable MXP. */
const char EnableMXP[] = { (char)IAC, (char)SB, TELOPT_MXP, (char)IAC, (char)SE, '\0' };
Write(apDescriptor, EnableMXP);
/* Create a secure channel, and note that MXP is active. */
Write(apDescriptor, "\033[7z");
pProtocol->bMXP = true;
pProtocol->pVariables[eMSDP_MXP]->ValueInt = 1;
}
else if ( aCmd == (char)WONT )
{
if ( !pProtocol->bMXP )
{
/* The MXP standard doesn't actually specify whether you should
* negotiate with IAC DO MXP or IAC WILL MXP. As a result, some
* clients support one, some the other, and some support both.
*
* Therefore we first try IAC DO MXP, and if the client replies
* with WONT, we try again (here) with IAC WILL MXP.
*/
const char WillMXP[] = { (char)IAC, (char)WILL, TELOPT_MXP, '\0' };
Write(apDescriptor, WillMXP);
}
else // The client is actually asking us to switch MXP off.
{
pProtocol->bMXP = false;
}
}
else if ( aCmd == (char)DONT )
pProtocol->bMXP = false;
else /* Anything else is invalid. */
bResult = false;
break;
case (char)TELOPT_ATCP:
if ( aCmd == (char)WILL )
{
/* If we don't support MSDP, fake it with ATCP */
if ( !pProtocol->bMSDP )
{
pProtocol->bATCP = true;
#ifdef MUDLET_PACKAGE
/* Send the Mudlet GUI package to the user. */
if ( MatchString( "Mudlet",
pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString ) )
{
SendATCP( apDescriptor, "Client.GUI", MUDLET_PACKAGE );
}
#endif /* MUDLET_PACKAGE */
/* Identify the mud to the client. */
MSDPSendPair( apDescriptor, "SERVER_ID", MUD_NAME );
}
}
else if ( aCmd == (char)WONT )
pProtocol->bATCP = false;
else /* Anything else is invalid. */
bResult = false;
break;
default:
bResult = false;
}
}
static void PerformSubnegotiation( descriptor_t *apDescriptor, char aCmd, char *apData, int aSize )
{
protocol_t *pProtocol = apDescriptor->pProtocol;
switch ( aCmd )
{
case (char)TELOPT_TTYPE:
if ( pProtocol->bTTYPE )
{
/* Store the client name. */
const int MaxClientLength = 64;
char *pClientName = alloca(MaxClientLength+1);
int i = 0, j = 1;
bool_t bStopCyclicTTYPE = false;
for ( ; apData[j] != '\0' && i < MaxClientLength; ++j )
{
if ( isprint(apData[j]) )
pClientName[i++] = apData[j];
}
pClientName[i] = '\0';
/* Store the first TTYPE as the client name */
if ( !strcmp(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString, "Unknown") )
{
free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);
/* This is a bit nasty, but using cyclic TTYPE on windows telnet
* causes it to lock up. None of the clients we need to cycle
* with send ANSI to start with anyway, so we shouldn't have any
* conflicts.
*
* An alternative solution is to use escape sequences to check
* for windows telnet prior to negotiation, and this also avoids
* the player losing echo, but it has other issues. Because the
* escape codes are technically in-band, even though they'll be
* stripped from the display, the newlines will still cause some
* scrolling. Therefore you need to either pause the session
* for a few seconds before displaying the login screen, or wait
* until the player has entered their name before negotiating.
*/
if ( !strcmp(pClientName,"ANSI") )
bStopCyclicTTYPE = true;
}
/* Cycle through the TTYPEs until we get the same result twice, or
* find ourselves back at the start.
*
* If the client follows RFC1091 properly then it will indicate the
* end of the list by repeating the last response, and then return
* to the top of the list. If you're the trusting type, then feel
* free to remove the second strcmp ;)
*/
if ( pProtocol->pLastTTYPE == NULL ||
(strcmp(pProtocol->pLastTTYPE, pClientName) &&
strcmp(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString, pClientName)) )
{
char RequestTTYPE [] = { (char)IAC, (char)SB, TELOPT_TTYPE, SEND, (char)IAC, (char)SE, '\0' };
const char *pStartPos = strstr( pClientName, "-" );
/* Store the TTYPE */
if (pProtocol->pLastTTYPE)
free(pProtocol->pLastTTYPE);
pProtocol->pLastTTYPE = AllocString(pClientName);
/* Look for 256 colour support */
if ( (pStartPos != NULL && MatchString(pStartPos, "-256color")) || MatchString(pClientName, "xterm") )
{
/* This is currently the only way to detect support for 256
* colours in TinTin++, WinTin++ and BlowTorch.
*/
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
/* Request another TTYPE */
if ( !bStopCyclicTTYPE )
Write(apDescriptor, RequestTTYPE);
}
if ( PrefixString("Mudlet", pClientName) )
{
/* Mudlet beta 15 and later supports 256 colours, but we can't
* identify it from the mud - everything prior to 1.1 claims
* to be version 1.0, so we just don't know.
*/
pProtocol->b256Support = eSOMETIMES;
if ( strlen(pClientName) > 7 )
{
pClientName[6] = '\0';
free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);
free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pClientName+7);
/* Mudlet 1.1 and later supports 256 colours. */
if ( strcmp(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString, "1.1") >= 0 )
{
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
}
}
else if ( MatchString(pClientName, "EMACS-RINZAI") )
{
/* We know for certain that this client has support */
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
}
else if ( PrefixString("DecafMUD", pClientName) )
{
/* We know for certain that this client has support */
pProtocol->pVariables[eMSDP_XTERM_256_COLORS]->ValueInt = 1;
pProtocol->b256Support = eYES;
if ( strlen(pClientName) > 9 )
{
pClientName[8] = '\0';
free(pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_ID]->pValueString = AllocString(pClientName);
free(pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString);
pProtocol->pVariables[eMSDP_CLIENT_VERSION]->pValueString = AllocString(pClientName+9);
}
}
else if ( MatchString(pClientName, "MUSHCLIENT") ||
MatchString(pClientName, "CMUD") ||
MatchString(pClientName, "ATLANTIS") ||
MatchString(pClientName, "KILDCLIENT") ||
MatchString(pClientName, "TINTIN++") ||
MatchString(pClientName, "TINYFUGUE") )
{
/* We know that some versions of this client have support */
pProtocol->b256Support = eSOMETIMES;
}
else if ( MatchString(pClientName, "ZMUD") )
{
/* We know for certain that this client does not have support */
pProtocol->b256Support = eNO;
}
}
break;
case (char)TELOPT_NAWS:
if ( pProtocol->bNAWS )
{
/* Store the new width. */
pProtocol->ScreenWidth = (unsigned char)apData[0];
pProtocol->ScreenWidth <<= 8;
pProtocol->ScreenWidth += (unsigned char)apData[1];
/* Store the new height. */
pProtocol->ScreenHeight = (unsigned char)apData[2];
pProtocol->ScreenHeight <<= 8;
pProtocol->ScreenHeight += (unsigned char)apData[3];
}
break;
case (char)TELOPT_CHARSET:
if ( pProtocol->bCHARSET )
{
/* Because we're only asking about UTF-8, we can just check the
* first character. If you ask for more than one CHARSET you'll
* need to read through the results to see which are accepted.
*
* Note that the user must also use a unicode font!
*/
if ( apData[0] == ACCEPTED )
pProtocol->pVariables[eMSDP_UTF_8]->ValueInt = 1;
}
break;
case (char)TELOPT_MSDP:
ParseMSDP( apDescriptor, apData );
break;
case (char)TELOPT_ATCP:
ParseATCP( apDescriptor, apData );
break;
default: /* Unknown subnegotiation, so we simply ignore it. */
break;
}
}
/******************************************************************************
Local MSDP functions.
******************************************************************************/
static void ParseMSDP( descriptor_t *apDescriptor, const char *apData )
{
char Variable[MSDP_VAL][MAX_MSDP_SIZE+1] = { {'\0'}, {'\0'} };
char *pPos = NULL, *pStart = NULL;
while ( *apData )
{
switch ( *apData )
{
case MSDP_VAR: case MSDP_VAL:
pPos = pStart = Variable[*apData++-1];
break;
default: /* Anything else */
if ( pPos && pPos-pStart < MAX_MSDP_SIZE )
{
*pPos++ = *apData;
*pPos = '\0';
}
if ( *++apData )
continue;
}
ExecuteMSDPPair( apDescriptor, Variable[MSDP_VAR-1], Variable[MSDP_VAL-1] );
Variable[MSDP_VAL-1][0] = '\0';
}
}
static void ExecuteMSDPPair( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
if ( apVariable[0] != '\0' && apValue[0] != '\0' )
{
if ( MatchString(apVariable, "SEND") )
{
bool_t bDone = false;
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
{
if ( MatchString(apValue, VariableNameTable[i].pName) )
{
MSDPSend( apDescriptor, (variable_t)i );
bDone = true;
}
}
}
else if ( MatchString(apVariable, "REPORT") )
{
bool_t bDone = false;
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
{
if ( MatchString(apValue, VariableNameTable[i].pName) )
{
apDescriptor->pProtocol->pVariables[i]->bReport = true;
apDescriptor->pProtocol->pVariables[i]->bDirty = true;
bDone = true;
}
}
}
else if ( MatchString(apVariable, "RESET") )
{
if ( MatchString(apValue, "REPORTABLE_VARIABLES") ||
MatchString(apValue, "REPORTED_VARIABLES") )
{
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( apDescriptor->pProtocol->pVariables[i]->bReport )
{
apDescriptor->pProtocol->pVariables[i]->bReport = false;
apDescriptor->pProtocol->pVariables[i]->bDirty = false;
}
}
}
}
else if ( MatchString(apVariable, "UNREPORT") )
{
bool_t bDone = false;
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX && !bDone; ++i )
{
if ( MatchString(apValue, VariableNameTable[i].pName) )
{
apDescriptor->pProtocol->pVariables[i]->bReport = false;
apDescriptor->pProtocol->pVariables[i]->bDirty = false;
bDone = true;
}
}
}
else if ( MatchString(apVariable, "LIST") )
{
if ( MatchString(apValue, "COMMANDS") )
{
const char MSDPCommands[] = "LIST REPORT RESET SEND UNREPORT";
MSDPSendList( apDescriptor, "COMMANDS", MSDPCommands );
}
else if ( MatchString(apValue, "LISTS") )
{
const char MSDPCommands[] = "COMMANDS LISTS CONFIGURABLE_VARIABLES REPORTABLE_VARIABLES REPORTED_VARIABLES SENDABLE_VARIABLES GUI_VARIABLES";
MSDPSendList( apDescriptor, "LISTS", MSDPCommands );
}
/* Split this into two if some variables aren't REPORTABLE */
else if ( MatchString(apValue, "SENDABLE_VARIABLES") ||
MatchString(apValue, "REPORTABLE_VARIABLES") )
{
char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( !VariableNameTable[i].bGUI )
{
/* Add the separator between variables */
strcat( MSDPCommands, " " );
/* Add the variable to the list */
strcat( MSDPCommands, VariableNameTable[i].pName );
}
}
MSDPSendList( apDescriptor, apValue, MSDPCommands );
}
else if ( MatchString(apValue, "REPORTED_VARIABLES") )
{
char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( apDescriptor->pProtocol->pVariables[i]->bReport )
{
/* Add the separator between variables */
if ( MSDPCommands[0] != '\0' )
strcat( MSDPCommands, " " );
/* Add the variable to the list */
strcat( MSDPCommands, VariableNameTable[i].pName );
}
}
MSDPSendList( apDescriptor, apValue, MSDPCommands );
}
else if ( MatchString(apValue, "CONFIGURABLE_VARIABLES") )
{
char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( VariableNameTable[i].bConfigurable )
{
/* Add the separator between variables */
if ( MSDPCommands[0] != '\0' )
strcat( MSDPCommands, " " );
/* Add the variable to the list */
strcat( MSDPCommands, VariableNameTable[i].pName );
}
}
MSDPSendList( apDescriptor, "CONFIGURABLE_VARIABLES", MSDPCommands );
}
else if ( MatchString(apValue, "GUI_VARIABLES") )
{
char MSDPCommands[MAX_OUTPUT_BUFFER] = { '\0' };
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( VariableNameTable[i].bGUI )
{
/* Add the separator between variables */
if ( MSDPCommands[0] != '\0' )
strcat( MSDPCommands, " " );
/* Add the variable to the list */
strcat( MSDPCommands, VariableNameTable[i].pName );
}
}
MSDPSendList( apDescriptor, apValue, MSDPCommands );
}
}
else /* Set any configurable variables */
{
int i; /* Loop counter */
for ( i = eMSDP_NONE+1; i < eMSDP_MAX; ++i )
{
if ( VariableNameTable[i].bConfigurable )
{
if ( MatchString(apVariable, VariableNameTable[i].pName) )
{
if ( VariableNameTable[i].bString )
{
/* A write-once variable can only be set if the value
* is "Unknown". This is for things like client name,
* where we don't really want the player overwriting a
* proper client name with junk - but on the other hand,
* its possible a client may choose to use MSDP to
* identify itself.
*/
if ( !VariableNameTable[i].bWriteOnce ||
!strcmp(apDescriptor->pProtocol->pVariables[i]->pValueString, "Unknown") )
{
/* Store the new value if it's valid */
char *pBuffer = alloca(VariableNameTable[i].Max+1);
int j; /* Loop counter */
for ( j = 0; j < VariableNameTable[i].Max && *apValue != '\0'; ++apValue )
{
if ( isprint(*apValue) )
pBuffer[j++] = *apValue;
}
pBuffer[j++] = '\0';
if ( j >= VariableNameTable[i].Min )
{
free(apDescriptor->pProtocol->pVariables[i]->pValueString);
apDescriptor->pProtocol->pVariables[i]->pValueString = AllocString(pBuffer);
}
}
}
else /* This variable only accepts numeric values */
{
/* Strip any leading spaces */
while ( *apValue == ' ' )
++apValue;
if ( *apValue != '\0' && IsNumber(apValue) )
{
int Value = atoi(apValue);
if ( Value >= VariableNameTable[i].Min &&
Value <= VariableNameTable[i].Max )
{
apDescriptor->pProtocol->pVariables[i]->ValueInt = Value;
}
}
}
}
}
}
}
}
}
/******************************************************************************
Local ATCP functions.
******************************************************************************/
static void ParseATCP( descriptor_t *apDescriptor, const char *apData )
{
char Variable[MSDP_VAL][MAX_MSDP_SIZE+1] = { {'\0'}, {'\0'} };
char *pPos = NULL, *pStart = NULL;
while ( *apData )
{
switch ( *apData )
{
case '@':
pPos = pStart = Variable[0];
apData++;
break;
case ' ':
pPos = pStart = Variable[1];
apData++;
break;
default: /* Anything else */
if ( pPos && pPos-pStart < MAX_MSDP_SIZE )
{
*pPos++ = *apData;
*pPos = '\0';
}
if ( *++apData )
continue;
}
ExecuteMSDPPair( apDescriptor, Variable[MSDP_VAR-1], Variable[MSDP_VAL-1] );
Variable[MSDP_VAL-1][0] = '\0';
}
}
#ifdef MUDLET_PACKAGE
static void SendATCP( descriptor_t *apDescriptor, const char *apVariable, const char *apValue )
{
char ATCPBuffer[MAX_VARIABLE_LENGTH+1] = { '\0' };
if ( apVariable != NULL && apValue != NULL )
{
protocol_t *pProtocol = apDescriptor ? apDescriptor->pProtocol : NULL;
/* Should really be replaced with a dynamic buffer */
int RequiredBuffer = strlen(apVariable) + strlen(apValue) + 12;
if ( RequiredBuffer >= MAX_VARIABLE_LENGTH )
{
if ( RequiredBuffer - strlen(apValue) < MAX_VARIABLE_LENGTH )
{
sprintf( ATCPBuffer,
"SendATCP: %s %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
apVariable, RequiredBuffer, MAX_VARIABLE_LENGTH );
}
else /* The variable name itself is too long */
{
sprintf( ATCPBuffer,
"SendATCP: Variable name has a length of %d bytes (exceeds MAX_VARIABLE_LENGTH of %d).\n",
RequiredBuffer, MAX_VARIABLE_LENGTH );
}
ReportBug( ATCPBuffer );
ATCPBuffer[0] = '\0';
}
else if ( pProtocol->bATCP )
{
sprintf( ATCPBuffer, "%c%c%c%s %s%c%c",
IAC, SB, TELOPT_ATCP, apVariable, apValue, IAC, SE );
}
/* Just in case someone calls this function without checking ATCP */
if ( ATCPBuffer[0] != '\0' )
Write( apDescriptor, ATCPBuffer );
}
}
#endif /* MUDLET_PACKAGE */
/******************************************************************************
Local MSSP functions.
******************************************************************************/
static const char *GetMSSP_Players()
{
static char Buffer[32];
sprintf( Buffer, "%d", s_Players );
return Buffer;
}
static const char *GetMSSP_Uptime()
{
static char Buffer[32];
sprintf( Buffer, "%d", (int)s_Uptime );
return Buffer;
}
/* Macro for readability, but you can remove it if you don't like it */
#define FUNCTION_CALL(f) "", f
static void SendMSSP( descriptor_t *apDescriptor )
{
char MSSPBuffer[MAX_MSSP_BUFFER];
char MSSPPair[128];
int SizeBuffer = 3; /* IAC SB MSSP */
int i; /* Loop counter */
/* Before updating the following table, please read the MSSP specification:
*
* http://tintin.sourceforge.net/mssp/
*
* It's important that you use the correct format and spelling for the MSSP
* variables, otherwise crawlers may reject the data as invalid.
*/
static MSSP_t MSSPTable[] =
{
/* Required */
{ "NAME", MUD_NAME }, /* Change this in protocol.h */
{ "PLAYERS", FUNCTION_CALL( GetMSSP_Players ) },
{ "UPTIME" , FUNCTION_CALL( GetMSSP_Uptime ) },
/* Generic */
{ "CRAWL DELAY", "-1" },
/*
{ "HOSTNAME", "" },
{ "PORT", "" },
{ "CODEBASE", "" },
{ "CONTACT", "" },
{ "CREATED", "" },
{ "ICON", "" },
{ "IP", "" },
{ "LANGUAGE", "" },
{ "LOCATION", "" },
{ "MINIMUM AGE", "" },
{ "WEBSITE", "" },
*/
/* Categorisation */
/*
{ "FAMILY", "" },
{ "GENRE", "" },
{ "GAMEPLAY", "" },
{ "STATUS", "" },
{ "GAMESYSTEM", "" },
{ "INTERMUD", "" },
{ "SUBGENRE", "" },
*/
/* World */
/*
{ "AREAS", "0" },
{ "HELPFILES", "0" },
{ "MOBILES", "0" },
{ "OBJECTS", "0" },
{ "ROOMS", "0" },
{ "CLASSES", "0" },
{ "LEVELS", "0" },
{ "RACES", "0" },
{ "SKILLS", "0" },
*/
/* Protocols */
/*
{ "ANSI", "1" },
{ "GMCP", "0" },
#ifdef USING_MCCP
{ "MCCP", "1" },
#else
{ "MCCP", "0" },
#endif // USING_MCCP
{ "MCP", "0" },
{ "MSDP", "1" },
{ "MSP", "1" },
{ "MXP", "1" },
{ "PUEBLO", "0" },
{ "UTF-8", "1" },
{ "VT100", "0" },
{ "XTERM 256 COLORS", "1" },
*/
/* Commercial */
/*
{ "PAY TO PLAY", "0" },
{ "PAY FOR PERKS", "0" },
*/
/* Hiring */
/*
{ "HIRING BUILDERS", "0" },
{ "HIRING CODERS", "0" },
*/
/* Extended variables */
/* World */
/*
{ "DBSIZE", "0" },
{ "EXITS", "0" },
{ "EXTRA DESCRIPTIONS", "0" },
{ "MUDPROGS", "0" },
{ "MUDTRIGS", "0" },
{ "RESETS", "0" },
*/
/* Game */
/*
{ "ADULT MATERIAL", "0" },
{ "MULTICLASSING", "0" },
{ "NEWBIE FRIENDLY", "0" },
{ "PLAYER CITIES", "0" },
{ "PLAYER CLANS", "0" },
{ "PLAYER CRAFTING", "0" },
{ "PLAYER GUILDS", "0" },
{ "EQUIPMENT SYSTEM", "" },
{ "MULTIPLAYING", "" },
{ "PLAYERKILLING", "" },
{ "QUEST SYSTEM", "" },
{ "ROLEPLAYING", "" },
{ "TRAINING SYSTEM", "" },
{ "WORLD ORIGINALITY", "" },
*/
/* Protocols */
/*
{ "ATCP", "1" },
{ "SSL", "0" },
{ "ZMP", "0" },
*/
{ NULL, NULL } /* This must always be last. */
};
/* Begin the subnegotiation sequence */
sprintf( MSSPBuffer, "%c%c%c", IAC, SB, TELOPT_MSSP );
for ( i = 0; MSSPTable[i].pName != NULL; ++i )
{
int SizePair;
/* Retrieve the next MSSP variable/value pair */
sprintf( MSSPPair, "%c%s%c%s", MSSP_VAR, MSSPTable[i].pName, MSSP_VAL,
MSSPTable[i].pFunction ? (*MSSPTable[i].pFunction)() :
MSSPTable[i].pValue );
/* Make sure we don't overflow the buffer */
SizePair = strlen(MSSPPair);
if ( SizePair+SizeBuffer < MAX_MSSP_BUFFER-4 )
{
strcat( MSSPBuffer, MSSPPair );
SizeBuffer += SizePair;
}
}
/* End the subnegotiation sequence */
sprintf( MSSPPair, "%c%c", IAC, SE );
strcat( MSSPBuffer, MSSPPair );
/* Send the sequence */
Write( apDescriptor, MSSPBuffer );
}
/******************************************************************************
Local MXP functions.
******************************************************************************/
static char *GetMxpTag( const char *apTag, const char *apText )
{
static char MXPBuffer [64];
const char *pStartPos = strstr(apText, apTag);
if ( pStartPos != NULL )
{
const char *pEndPos = apText+strlen(apText);
pStartPos += strlen(apTag); /* Add length of the tag */
if ( pStartPos < pEndPos )
{
int Index = 0;
/* Some clients use quotes...and some don't. */
if ( *pStartPos == '\"' )
pStartPos++;
for ( ; pStartPos < pEndPos && Index < 60; ++pStartPos )
{
char Letter = *pStartPos;
if ( Letter == '.' || isdigit(Letter) || isalpha(Letter) )
{
MXPBuffer[Index++] = Letter;
}
else /* Return the result */
{
MXPBuffer[Index] = '\0';
return MXPBuffer;
}
}
}
}
/* Return NULL to indicate no tag was found. */
return NULL;
}
/******************************************************************************
Local colour functions.
******************************************************************************/
static const char *GetAnsiColour( bool_t abBackground, int aRed, int aGreen, int aBlue )
{
if ( aRed == aGreen && aRed == aBlue && aRed < 2)
return abBackground ? s_BackBlack : aRed >= 1 ? s_BoldBlack : s_DarkBlack;
else if ( aRed == aGreen && aRed == aBlue )
return abBackground ? s_BackWhite : aRed >= 4 ? s_BoldWhite : s_DarkWhite;
else if ( aRed > aGreen && aRed > aBlue )
return abBackground ? s_BackRed : aRed >= 3 ? s_BoldRed : s_DarkRed;
else if ( aRed == aGreen && aRed > aBlue )
return abBackground ? s_BackYellow : aRed >= 3 ? s_BoldYellow : s_DarkYellow;
else if ( aRed == aBlue && aRed > aGreen )
return abBackground ? s_BackMagenta : aRed >= 3 ? s_BoldMagenta : s_DarkMagenta;
else if ( aGreen > aBlue )
return abBackground ? s_BackGreen : aGreen >= 3 ? s_BoldGreen : s_DarkGreen;
else if ( aGreen == aBlue )
return abBackground ? s_BackCyan : aGreen >= 3 ? s_BoldCyan : s_DarkCyan;
else /* aBlue is the highest */
return abBackground ? s_BackBlue : aBlue >= 3 ? s_BoldBlue : s_DarkBlue;
}
static const char *GetRGBColour( bool_t abBackground, int aRed, int aGreen, int aBlue )
{
static char Result[16];
int ColVal = 16 + (aRed * 36) + (aGreen * 6) + aBlue;
sprintf( Result, "\033[%c8;5;%c%c%cm",
'3'+abBackground, /* Background */
'0'+(ColVal/100), /* Red */
'0'+((ColVal%100)/10), /* Green */
'0'+(ColVal%10) ); /* Blue */
return Result;
}
static bool_t IsValidColour( const char *apArgument )
{
int i; /* Loop counter */
/* The sequence is 4 bytes, but we can ignore anything after it. */
if ( apArgument == NULL || strlen(apArgument) < 4 )
return false;
/* The first byte indicates foreground/background. */
if ( tolower(apArgument[0]) != 'f' && tolower(apArgument[0]) != 'b' )
return false;
/* The remaining three bytes must each be in the range '0' to '5'. */
for ( i = 1; i <= 3; ++i )
{
if ( apArgument[i] < '0' || apArgument[i] > '5' )
return false;
}
/* It's a valid foreground or background colour */
return true;
}
/******************************************************************************
Other local functions.
******************************************************************************/
static bool_t MatchString( const char *apFirst, const char *apSecond )
{
while ( *apFirst && tolower(*apFirst) == tolower(*apSecond) )
++apFirst, ++apSecond;
return ( !*apFirst && !*apSecond );
}
static bool_t PrefixString( const char *apPart, const char *apWhole )
{
while ( *apPart && tolower(*apPart) == tolower(*apWhole) )
++apPart, ++apWhole;
return ( !*apPart );
}
static bool_t IsNumber( const char *apString )
{
while ( *apString && isdigit(*apString) )
++apString;
return ( !*apString );
}
static char *AllocString( const char *apString )
{
char *pResult = NULL;
if ( apString != NULL )
{
int Size = strlen(apString);
pResult = (char *) malloc(Size+1);
if ( pResult != NULL )
strcpy( pResult, apString );
}
return pResult;
}