With the development of Mudtrek underway, I needed to build an ANSI telnet terminal for Cocoa. GCDAsyncSocket and NSTextView simplified the heavy lifting of the network communication and text rendering, but finding documentation on the telnet protocols and ANSI standards was more difficult (especially for the niche purpose of a MUD client). Here is a breakdown of what's involved:
Building a MUD Client
Or, an ANSI-compatible telnet terminalThis page provides an overview of development considerations for writing an ANSI-compatible telnet terminal / MUD client. Also see Cryosphere's MUD protocol article (mirrored here).
The reason I'm writing this is to help preserve this information for people interested in it, since I did not find much in my own search.
Contents
- Overview
- Connecting to the server
- Telnet commands
- ANSI Codes
- Supporting sound, other protocols
- Other links
- Cryosphere Guide
What is a MUD client?
Essentially, a telnet terminal with ANSI color support and additional tools for text-based gameplaying built ontop of it. As such, to write a MUD client you're going to need to learn the fundamentals of the telnet protocol and ANSI standard.
What an ANSI Telnet terminal does
The specific features that differentiate a MUD client from a regular telnet terminal (macros, triggers, mapping, hotkeys, etc.) won't be covered here since those are relatively straightforward to implement at your own whims. Instead this will be a review of the basics of getting the telnet terminal itself running. Its operation is fairly simple:
- Connect to a MUD server and read incoming data
- Output that data to the screen as ASCII text
- Unless you receive an IAC (Interpret As Command) byte, in which case you handle the command
- Or you receive an ANSI CSI (Control Sequence Introducer), in which case you interpret that small bit of text as an ANSI command and format your terminal output accordingly
- Accept input / commands from the player and send them to the server
Connect to the MUD server
You can use a preexisting network framework or socket layer for connectivity. (MUDTrek uses GCDAsyncSocket). You will establish a connection for the duration of the gameplay and read data from the socket buffer / input stream continuously. (Are you looking for MUDs? Visit MUDConnect)
Print data to the screen
Nearly everything you'll receive from the MUD server is intended for the screen. You'll get large amounts of ASCII text, and besides the exceptions below, all of this is to be displayed. Be careful with linebreaks: you may need to replace CR/LF endings with newline characters to properly display the text. If you're seeing junk characters or garbage data mixed in with the normal text, see the IAC/CSI notes below.Interpret Telnet Commands
Telnet is a very simple protocol to deal with:- Check incoming data from the MUD for the IAC (Interpret As Command) byte, which is 0xff (255)
- If you see a 0xff / 255 byte, the next byte will be a Telnet command
- If the command is within the byte range of 251-254, the third byte will be the option code for the command
- If the command is byte 250 (SB), it is the beginning of 'subnegotiation parameters', and each byte that follows is a parameter, until you hit another 0xff (IAC) and then byte 240 (SE)
- If you see a double IAC (0xff 0xff) you can treat the second 0xff as normal data and ignore the first
Interpret ANSI codes
The next step is to check for ANSI codes. These will come in the form of a Control Sequence Introducer (CSI) followed by bytes to specify the code, followed by a delimiter to wrap things up. Generally for MUDs you will check for ANSI codes in this way:- Scan ASCII text for the ESCAPE byte (0x1b / 27) followed by an open left bracket character ('[')
- Scan the bytes following this two-character CSI to determine the code
- Look for the end of the ANSI sequence, which will often be the 'm' character but can be any ASCII byte from 64-95 ('@' - '~')
Again, if you're seeing garbage characters, then your telnet client is not properly stripping these sequences from the output.
Because there are quite a few codes to the ANSI standard, the extent to which you implement and support them is up to you. The full list of codes is available here: wiki page.
Supporting sound, and other MUD protocols
In addition to the protocols discussed here, several other MUD-specific protocols have been established and put into use on some MUD servers. An example is MUD Sound Protocol (MSP) which when supported by both server/client allows audio effect playback.Send commands to the server
This is fairly straightforward. A good grasp of TCP-based networking will come in handy here, however ultimately you just have to send the user's text commands (followed by a carriage return or a newline) to the server.Other Info
Useful reading material:ANSI Escape Codes
RFC 854
RFC 855
RFC 857
RFC 885
RFC 1091
RFC 1073
Telnet option codes: Telnet option codes
Questions? Feel free to contact me
Everything You Wanted To Know About Mud Client/Server Interaction But Were Afraid To Ask
by Abi Brady
The Telnet Protocol
Muds communicate with their clients with the Telnet protocol. You shouldn't write a mud client or server without reading RFCs 854 and 855, which specify this. Most clients and servers do not implement the full protocol. In particular, it is rare for options to be renegotiable. Accordingly, both ends should be tolerant.The Telnet protocol uses 0xff as an escape character, both ways. To send the data-byte 0xff, a client or server MUST send 0xff 0xff. (IAC IAC).
If a program justs want to ignore the telnet stuff, then it's easy to do.
IAC IAC -> data byte 255 IAC WILL/WONT/DO/DONT xxx -> ignore IAC SB xxx ... IAC SE -> ignore IAC other -> ignore
The telnet parser MUST be written as a state machine on a layer on its own - there's no reason why telnet sequences are not allowed to come in the middle of an escape sequence. Do not assume that telnet or ansi sequences are not split across packets.
The client MUST defer sending its own telnet negotiation requests until such time as it knows the server will be able to understand them. This means getting WILL or DO request for an option other than ECHO. (Many Dikus and derivatives are broken such that if the client sends IAC WILL LINEMODE, they fail to filter out the LINEMODE (it being 34, and thus the printable character "), and think it intended as part of the user name. If the server sends IAC sequences to a client that it doesn't know supports it, the worst that can happen is that the client displays crap on the screen.)
The character set used by the Telnet protocol is US-ASCII. Servers MAY use another character set appropriate to the language of the mud (for example, ISO-8859-1, KOI8-R) by default, but there SHOULD be an option to send only ASCII. Obviously this requirement doesn't make sense for languages where transliteration is expensive. Clients MUST be able to deal with the usual encoding for their locale. For example, a mud client aimed at western users MUST be able to deal with ISO-8859-1.
(According to the Telnet spec, only US-ASCII is allowed on the wire. However, there's no useful way of negotiating - RFC 2066's CHARSET option is hardly implemented, and ISO-2022 is useless for most character sets. Further, even binary mode is not useful, as it changes the end-of-line codes from the standard values to platform-specific ones. Therefore, BINARY mode MUST NOT be requested by servers or clients, however clients MAY accept it if offered.)
Ansi Sequences
Telnet makes no guarantees that what is at the other end is a terminal capable of parsing ANSI sequences. However any mud client that cannot is clearly deficient. In the survey, a few of these were discovered - all of which are unmaintained. As long as the server keeps itself to a limited set, all is safe. See ECMA 48 (downloadable for free as a pdf) for the full details, the minimal collection is -
code | meaning |
ESC[0m | reset all attributes and colours to default |
ESC[1m | bold |
ESC[3m | italic (unreliable) |
ESC[4m | underline (unreliable) |
ESC[22m | bold off (unreliable) |
ESC[23m | italic off (unreliable) |
ESC[24m | underline off (unreliable) |
ESC[30m ... ESC[37m | black fg .. white fg |
ESC[39m | default fg (unreliable) |
ESC[40m ... ESC[47m | black bg .. white fg |
ESC[49m | default fg (unreliable) |
The client MUST default to white-on-black or some other light-on-dark colour scheme where neither the background nor the foreground is one of the 7 other standard colours. If it doesn't, then the server has no way of using colour safely. The client MAY allow the user to customise their colour scheme to be black-on-white or however they like.
ESC[m is defined to be an alias for ESC[0m, but quite a lot of mud clients don't know this. Many even have problems with parsing sequences like ESC[0;1;3;32;45m. These MUST be supported by clients, but SHOULD NOT be sent by servers.
XFree86 xterm (when compiled to) supports a very useful extension to a 256-colour thing, including a 6x6x6 colour cube. To set fg, do ESC[38;5;nm, and bg ESC[48;5;nm. There is no need in a mud server to worry about programming the palette, the mud client should do any reprogramming of the palette to the standard one if possible. 0..15 are the regular colours, once in normal form, one in bright form. 16-231 are a 6x6x6 colour cube. To get the offset, (red * 36) + (green * 6) + blue. Finally, 232-255 is a greyscale. 232 is very dark gray, 255 is very light gray. See xterm's 256colres.h for the exact RGB values.
Any other sequences than ESC[...m are not implemented by most mud clients and SHOULD NOT be used unless known safe through other means. This includes cursor movement.
Input and Echo
Local echo MUST be on by default. Many muds are designed to work with telnet, and expect echo to be on, unless they turn it off. This means that after a prompt, they will not supply their own newline, having expected the client to have put one in the buffer for them.broken client | nice client | |||||||
user sees... | > |
A "turn local echo off always" MAY be provided, but the newline must still be printed even if this is turned on. Likewise, if the mud has turned echo off, you MUST NOT put anything into the buffer, as the mud supplies its own newline.
Telnet Options - Echo
The mud sends "IAC WILL ECHO", meaning "I, the server, will do any echoing from now on." The client should acknowledge this with an IAC DO ECHO, and then stop putting echoed text in the input buffer. It should also do whatever is appropriate for password entry to the input box thing - for example, it might * it out. Text entered in server-echoes mode should also not be placed any command history.When the mud wants the client to start local echoing again, it sends "IAC WONT ECHO" - the client must respond to this with "IAC DONT ECHO".
See RFC 857.
Prompts
Most muds (aber, dikus, lps, but not mushes or moos), use prompts. These are tricky things. The server wants the prompt to be displayed to the left of the input line. In a terminal-based mud client this often no problem to arrange, and no special prompt detection is needed. However, GUI apps will need to be able to distinguish a prompt from a line that just hasn't come in yet.The usual way of doing this is (a) to send IAC GA after a prompt, or (b) for the mud to send IAC WILL TELOPT_EOR, then the client to send IAC DO TELOPT_EOR, and then the mud will send IAC EOR after every prompt. (see RFC 885 for EOR). According to the tinyfugue documents, some muds use "*\b" as a prompt terminator. I have never seen this in the wild.
This is all well and good, except for the fact that some lpmuds, and possibly others, assume some quite bizarre semantics at the client end. They'll assume that the text up to the prompt terminator be spirited away into some other prompt buffer - and any text they send from then on be displayed before the prompt.
sent from mud | expected to be displayed | displayed under 'telnet' |
100/100>[IAC GA] | 100/100> | 100/100> |
You are hungry.\r\n | You are hungry. 100/100> | 100/100>You are hungry. |
This is indeed what tinyfugue does, and a few others. If done carefully, it needn't break muds that are doing it sensibly.
The sensible way to do this, done in Abermuds and perhaps a few others, is to send "100/100>\rYou are hungry.\r\n100/100>". This has the downside that not all the prompt gets wiped if the message is shorter than the prompt. ESC[2k could be used for this if the mud knows it has a client that supports it. This does lead to a nasty race condition, though, so perhaps the magic prompt marking approach does have merits.
Client Identification
This is specified in RFC 1091. Server sends IAC DO TELOPT_TTYPE, client sends IAC WILL TELOPT_TTYPE, server sends IAC SB TELOPT_TTYPE SEND IAC SE, client sents IAC SB TELOPT_TTYPE IS [terminal-type] IAC SE.The client should not send out a useless generic name like "ansi" or "vt102" (or worse, "linux"). Instead it should send out a name like "zmud", "mushclient", "lyntin". This allows the authors of the mud server to make decisions based on what the mud client is, and what the known capabilities are. It might be desirable to encode version number of the client there in some way, too, but there's no precedent for doing this.
If the server doesn't understand the terminal type, it can request another one, and the client can go through a list. For example, zmud, vt102, ansi, unknown.
Window Size
The server may need to know how big the clients window size is, for various reasons - where to wordwrap, how much to page, how big to make tables, etc. For this reason the client should implement the NAWS option,The server sends IAC DO TELOPT_NAWS, the client sends IAC WILL TELOPT_NAWS. It then immediately, and whenever the window size changes (even if its just a case of the window staying the same size and the font size changing), sends the server the window size. The server makes no requests. I've seen some clients that agree to do NAWS and then await a IAC SB TELOPT_NAWS SEND IAC SE from the server before actually sending it out. This is wrong wrong wrong.
The format of the NAWS subnegotiation is specified in RFC 1073. To quote -
The syntax for the subnegotiation is: IAC SB NAWS WIDTH[1] WIDTH[0] HEIGHT[1] HEIGHT[0] IAC SE As required by the Telnet protocol, any occurrence of 255 in the subnegotiation must be doubled to distinguish it from the IAC character (which has a value of 255).Not doubling the 255 is a common error that doesn't show up very often, but can lead to the telnet stack getting into an inconsistent state, and possibly ignoring all future input. Also remember, that the width and height are in network byte order (big-endian).
Titlebar Setting
Xterm and other terminals support setting the titlebar, with the following sequence.\33]0;
string\a
This could be used by a mud to display status info that isn't appropriate for a prompt,
such as player name and mud name and location within the mud.
Mud Specific Protocols
Compression : MCCP
MCCP is quite a nice spec that provides a way to compress data sent from the mud to the client with zlib. Quite wide client and server support.Hypertext : Pueblo
Pueblo was the first client to support hypertext stuff. I can't find any documentation on the protocol they used. It's supported by Pueblo, and partial support in mushclient.Hypertext : MXP
MXP is an open spec supported by both zmud and mushclient. It's implemented as a telnet option and is quite nice to implement on a server-side. Client-side of course will be rather tricky.Hypertext : IMP
IMP appears to be a reinvention of MXP. Only one client (fireclient) supports it, there's no formal spec, it doesn't use the telnet protocol as its framework.Hypertext : MIP
This closed spec has an obnoxious licence that forbids technical commentary of it. Only one client (portal) supports it.General : MCP
MCP is the Mud Client Protocol. It's oriented towards MOOs, so much so that it is actually impossible to implement on a mud with prompts. There seems to be nobody to contact about it.It's a metaspec which various packages can be placed on top of. For example, there's a module for sending files to the client for local editing, another for transferring userlists, one for sending timezone information to the server.