Spectrum NetPlay v1.0

(C) by Vladimir Kladov, 2005

What is Spectrum NetPlay? This is a simple networking protocol on base of TCP/IP sockets to communicate between Spectrum emulators. Several players can start emulators on their computers (PC, MAC, Sun, mobile phone - no matter) and connect to the one selected as a server. Then, the server sends to connected clients starting snapshot via a socket and then each 1/50 second it sends to each client a small portion of data block in Replay Action (RZX) format. I.e., the server is working in a Replay Action recording mode, and all clients in Replay Action playing mode, and all the players see the same picture on their displays (if there is no hidden moves, see below). Also, clients send their Spectrum keyboard state, when it is changed to the server. The server filters keys from clients depending on the NetPlay mode selected, providing different types of gaming. In simultanous mode, Spectrum keyboard is divided between players, and they all are playing the same game at the same time (e.g. tennis, 2-players tetris or any other two-players games, where players are playing in cooperative or antagonistic games). In step-by-step mode, not the keyboard, but the time is divided, i.e. each portion of time only one of players owns entire Spectrum keyboard until he finished his turn (card games, monopoly, tactic games). For this type of games, movements of a client can be visible for other people or hidden for them (though clients who does not included in the game can be declared as viewers and though they are not playing at all, they can see movements of all the players).

The protocol to communicate between Spectrum Netplay server and clients is not very hard. It contains mainly of not too large different strings or data blocks.

Any data block format
Spec<num><len><data> Spec - 4 chars 'Spec' to identify start of any message between server and client. <num> - 4 bytes block number (1, 2, ... ). Both server and client must put received blocks into a queue, and handle it only in the icrement order. This block number can be used on callback send sometimes (when the client informs the server, which data already are handled).

<len> - 4 bytes integer. It is the size of the following <data> block. It is possible that internally in the socket or on TCP/IP protocol level some blocks will be concatenated (or divided), so it is necessary to divide such concatenations on several parts programmatically. This length field helps in such case.

<data> - data block of size <len> bytes, content is described in a table below.

So, if a client sends string 'U' as a message numer 100, entire message block will be encoded as:

'Spec', 64h, 0, 0, 0, 2, 0, 0, 0, 'U', 0

Server sends:
Type | Content | When sent, what should client do.
ASCIZ | Spectrum NetPlay xxx... :<userlist><#01>idclient xxx... - any characters except ':' which is starting userlist <userlist> contains of strings trailing with character #13 (caret), each having following data: id<space>IP<space>[Nickname][<#9>mode] (#9 - tabulate character)

where:
id - unique number identifying the user (used to send private messages);
IP - IP of the client correspondent to the user (may be not unique);
Nickname is optional username (32 characters or shorter)
mode has following format:

[A][n][Cxxxxxx]

where
A - user is an arbiter
n - if present, then user is player numer n
Crrggbb - user messages color (xxxxxx - hexadecimal color value)

idclient - is the unique id assigned to the user, receiving this message.

The first time when a client is connected, and each time when user list is changed on the server. The client should use the userlist received from the server to show it in its userlist window.
CHAR or ASCIZ | bye (only the first character is taking into consideration, all other are ignored) | Bye command. The server is disconnecting, so clients should disconnect too, informing the user that is happaning.
ASCIZ | /text | Any message from the server including broadcast/private chat messages from one user to all/certain other. Client should show this message as is (may be adding date/time as a prefix).
ASCIZ | <sender id>/text | Private message sent to the sender to certain users is sent from the server to those ones. Client should show this message as is (may be adding date/time as a prefix and replacing <sender id> with coreespondent user identification).
CHARs + BYTES | D<ext><data bytes> The snapshot starting replay action. Following letter D, 3 letters <ext> are indicating snapshot filetype (e.g., Z80), then the snapshot itself. | When client is connected and received already userlist the first time, and any time when a player on server is loading another snapshot into its emulator, starting another playing session. The client emulator should load the snapshot and be ready to play Replay Action sequence following it.

The client must send a confiramtion <d> to the server (the server uses such confirmation to make a pause until all the clients have obtained the snapshot and be ready for playing Replay Action stream).

CHARs + BYTES | d<ext><len><data bytes> The snapshot starting replay action. Following letter d, 3 letters <ext> are indicating snapshot filetype (e.g., Z80), then 4 bytes <len> containing size of uncompressed data, then the snapshot itself. | Compressed snapshot . The server do not use compression, if the client have noticed that it do not want to receive compressed snapshots.
| Please note that D and d blocks (and any other blocks) are sending without dividing it onto several small data blocks. So developers should be ready to receive entire blocks even of very large size. Otherwise they can meet blocks divided as using socked desides, and such data can not be interpreted correctly in most cases.
CHAR + BYTES | I<num fetches><incount><input bytes> <num fetches> - 2 bytes, as usual for RZX (numder of Z80 instruction fetches before next INT signal). Values 0 and 1 are also allowed. <incount> - 2 bytes, as usual for RZX (number of input bytes, or 65535, if it is the same as for previous frame, in such case input bytes are omitted to minimize data).

<input bytes> - each byte corresponds to one IN (or INI, INIR) instruction as usual for RZX.

Next one portion of RZX input correspondent to a single frame. The only difference from RZX format is that interrupt must be emulated at the end of frame ( at start of the next frame, also see block <F> below).
CHAR + BYTES | i<len><num fetches><incount><input bytes> all fields are the same as for <I> block. | Next compressed portion of RZX input correspondent to a single frame.
CHAR + BYTES | F<num fetches><incount><input bytes> all fields are the same as for <I> block. | Next one portion of RZX input correspondent to a single frame, but without signalling INT at the end of the frame.
CHAR + BYTES | f<len><num fetches><incount><input bytes> all fields are the same as for <I> block. | Next compressed portion of RZX input correspondent to a single frame, but without signalling INT at the end of the frame.
ASCIIZ | M<mode> <mode> can be following: T[n] - time separate mode, n is Id of the active player; H[n] - time separate time with hidden movements, n is Id of the active player; K - keyboard separate mode for n players; | This is a current playing mode. It is send from the server near the beginning of play session but after sending userlist first time. And it can be send later any time, when the mode is changed for a player (by a player on the server or by another client having Arbiter rights).
CHAR | P | Pause to all clients. Mainly it is not necessary to send a special command to clients to make a pause, just stop sending <I>/<F> blocks, and clients should go to waiting mode. But this command helps to distinct a case when a connection become unstable from real pause better. Client can not pause by itself as usual, it must send its <P> command to the server, and then the server decides if to stop.

To resume from the pause, the server just sends the next <D> or <I> data block.

2 CHARs | K<keyboard state> <keyboard state> - 9 bytes array of the Spectrum Keyboard and Kempston joystick port state in the order: port #FEFE; port #FDFE; port #FBFE; port #F7FE; port #EFFE; port #DFFE; port #BFFE; port #7FFE; port #1F | Client could use this info to show which keys are pressing on the keyboard by all the players (this keyboard/joystick state is not send to all the clients while playing in "hidden moves" mode, but client "can" restore such info partially while replaying frames. Anyway, it should not do so in "hidden moves" mode, making moves really hidden for other players).
Client sends:
Type | Content | When is sent, what server do
ASCIIZ | Spectrum NetPlay xxx...[*][G]:nickname[<#9>Crrggbb] * - if the client supports UCL compression, it should pass the '*' character here, in such case the server can send snapshot for it compressed. If the client supports ZLib compression, it can place character '!' here. In any other case, the server will always send snapshot/input data blocks to the client always uncompressed. UCL in many cases gives better compression (it is used in known UPX executable packer).

G - if the client supports GFX (256 Colors games), the server can also send Gfx data with a snapshot.

The client must send such string immediately when connected as a first string. And it can be send any time when the player changes his nickname/color.

It is not recommended to change compression parameters on-fly: it is better to disconnect first from the server and then reconnect with new parameters.

ASCIIZ | /text | Broadcast message (chat). Resent to all other connected clients (including viewers) adding a nickname/IP of a sender as a prefix.
ASCIIZ | <user id list>/text <user id list> - is a list of unique id of target users, so the first id is always starting from a digit (0..9). Ids in the list are separated with comma (',') and the list is finishing with chararcter '/'. | Send private message to marked users only, always preceding with unique id of the sender.
CHAR + BYTEs | K<9 bytes> Spectrum Keyboard and Kempston joystick port state in the order: port #FEFE; port #FDFE; port #FBFE; port #F7FE; port #EFFE; port #DFFE; port #BFFE; port #7FFE; port #1F | Client should send this state only when it is changed, and only it is active.
CHAR | U | Unsynchronized. Client can send this request to resend <D> data block before further playing. The client should not send this request very often (or it has broken in part of RZX replaying).
CHAR | P | Pause request. Client should never pause itself, but send <P> request to a server.
CHAR | W | Like Pause request above, but the client sends it only in Time-separated mode, when it is made active, and enters to a loop of waiting until the player is confirmed his activation (this is optional feature of a client therefore).
CHAR | R | Client sends this message to a server to notify it that the user confirmed his activation and playing can continue.
CHAR | f<num> <num> - 4 bytes integer, number of the last frame replayed. | Client sends this message about 5 times a second to get know for a server if all the clients are time synchronized well. If at least one of clients is later then 25 frames (1/2 second), the server can hold executing for some time. In result, playing is freezing for some short period and then continued.
CHAR | C | Take control request. Client sends this request in time-divide playing mode, if the player decides that it is time to get a keyboard control for him but a control was not passed to him as game rules claim. But only an arbiter can decide if to satisfy such request. While such request is not answered, game is locked.
CHAR | N[id] id - unique identifier of a client to pass control to. If omitted, the next player is giving a control. | Pass control request. Client sends this request in time-divide playing mode, to take a control to a next (or to a certain) player. Until request satisfyed, game is locked. Only the arbiter can satisfy the request.
ASCIIZ | A<command> not implemented yet | If a user is assigned to be an arbiter, this command can be issued. The arbiter has rights to change client mode or state for all users,

To provide reliable speed over the network, the server should send frame info (<F> or <f>) 50 times a second, and set NO_DELAY flag for TCP connection. Otherwise, most of such blocks will be send coalesced, and in result client will not work sufficiently smooth, but with very jerkily video and sound. Though such option is not recommended for usual networking and leads to impair network bandwidth, this is the only possible way to provide Net Play feature using a technique "server playing & recording, clients replaying recorded sequence like video". In most cases clienta are sending data blocks 5-10 times less often then the server, and only of small sizes, so no problem to use this option too to send data from clients to the server. Moreover, this option should be used there too, to provide the server with the info about last frame played at the time, without too big delaying.