Instead having a DirectX based network system, we use a dedicated Server.
The Server component is only responsible to broadcast messages between the Clients so all TTDPatch Clients work in sync. Unlike traditional TTD, network joining will work while the first player is already in a game, the other players will get synced.
Both Server and Client have a framecounter, every action will take place at a certain framenumber.
A client sends actions to the server, which then decides at what frame the action on a client should take place. (The client doesn't cache the data, so the server has to send the data back.) The server is as well responsible to initiate a client sync and as well check the random gen number of the clients from time to time, if they are in sync, if not the server initiate a sync.
A client will only advance to the next frame if the server sends a heartbeat packet.
The heartbeat packet is created every 27ms, at a configurable duration the heartbeat packet with randomgen request is send, a client then has to answer with a random gen number package and the server checks each active client if the random number matches with the other client.
Note: The action data for a frame has to arrive directly before the heartbeat package for that frame number.
A network packet has a variable length and is always in little endian format.
Each package start with:
<u32 framenumber> <u8 type>
|0x01||Action Data||The raw action data of ttd follows||<u8 len> <u8 data*len>|
|0x02||Action Data End Marker||Send after all actions of this frame got send||-|
|0x30||Heartbeat||Send by the server to a client||-|
|0x31||Heartbeat with rnd request||with additional requesting a random number||-
|0x40||Random gen value||Answer to packet 0x31||<u32 random>|
|0x44||Get sync package||Request by the server to get the Gameplay data||-|
|0x45||Sync Package||Gameplay (Savegame) data||<u32 len> <u8*len of gamedata>|
|0x46||Sync heartbeat||Only send when a client gets resyced||-
|0x80||Quit Server||Quits the server application||<u8*16 server password>|
|0x81||KickBan||Kicks the player x from the server and deny reconnection||<u8 playerslot> <u8*16 server password>|
|0x82||Kick||Kicks the player x from the server||<u8 playerslot> <u8*16 server password>
|0xF0||Server Password||Sends the server password to join||<u8*16 server password for joining>|
|0xF1||Free Slot Messages||Server shows current slots||<u32 len of following data> <u8 free slot mask> <u8 password mask> <zero terminated strings follow for all player slots>|
|0xF2||Set Player Slot||Client requests a player||<u8 player slot to use> <16x u8 password> <u8 len of string> <string: Name> <00>
Password is filled with zero to match 16 Bytes, all zero means no password.
|0xF3||Player Accepted||If the slot is free and the password is right|
|0xF4||Player Not Accepted||If there is an error, slot not free||<u16 error id>|
|0xF5||Newgrfmap||A map of used grfs||<u32 len of following data> Pairs of <u8*16 md5sum> <string: newgrf filename> <0x00> <string: grf name> <0x00>|
|0xFF||Connect Error||Sends an error to the client||<u16 error id>|
Client connecting to server
Each connecting has some kind of identifier, the document calls it connection id.
After a client connects, it's in the handshake phase.
The client sends hard coded string, "TTDPATCH" + <u32 version of TTDPatch>
If the server doesn't get this string in 30secs after a established connection, it closes the connection.
The server then answers with "TTDPATCHSERVER" + <u32 serverversion> + <u16 error id>
If there is an error, this means an error id <> 0, the server will terminate the connection.
The server now accepts service packets in the range 0xF0 till 0xFF.
The server sends now it's current newgrfmap via packet 0xF5, if it's the first client, this will be simple a empty map, with the length of 0.
The client sends it's own newgrfmap (0xF5) to the server or terminates the connection.
The server may now send 0xFF Connect error to the client and disconnect if the newgrfs of the client don't fit.
The server sends a free slot message (0xF1) telling the client what Player slots are free.
Each free Player Slot sets the bit field, if a player slot has a password, it sets the password bit field.
As soon as the server receives a Set Player Slot (0xF2) it checks the password if it matches,
and sends (0xF3) if it matches or (0xF4) if it's wrong.
If there is already a game, the player will now get synced,
otherwise the connection id will be set for this the player slot.
Syncing on the server
The connection id will removed from the player slot.
The server broadcasts a Slot (0x44) message to all other player slots with a framenumber.
All Action Data a server transmits will be recorded now.
The server waits to get a (0x45) Sync Package, this will be transmitted to the new/re synced client.
Additional all ActionData will be transmitted with sync heartbeats (0x46, no 27ms wait) up to the framenumber.
The connection id will be restored from the player slot.
Heartbeats on the server
If the heartbeat is enabled, every 27ms:
Server Framenumber is increased.
If there is closed buffered action date , in all buffered packets the framenumber will be set to the new server Framenumber.
The buffered data is broadcasts to all player slots.
A heartbeat package will be send, every 100 heartbeat packets the Heartbeat type 0x31 is used, otherwise 0x30.
If heartbeat 0x31 is used, every player with a connection id has to send the random gen value (0x40) for the framenumber that 0x31 specified. The server looks if there is one client which has a different value. If a client has a different random gen value, it needs a re sync.
Server receives Data from Client
It stores the Action Data (0x01) packages in a buffer until it reaches an end of action data marker (0x02). It then closes the buffer so it can be send by the heartbeat part.
A client sends player Action Data with it's current frame number but doesn't execute this Action Data. If a frame ends and there was Action Data send, it sends an 0x02 message to indicate the end.
Instead doing the 27ms wait, it checks for the next heartbeat, sync or action data packet.
If it receives an 0x45 sync package, it will load the gamedata.
If it receives a get savegame message, it will create an 0x45 sync package.
As soon as the Heartbeat package arrives, the remaining time for 27ms wait will be waited if it's an normal heartbeat package otherwise (a 0x46 package) there is no wait , the frame gets executed and all received action for this frame number will be applied.
It will send the random gen number if the heartbeat package was 0x31.
If there is no heartbeat package in the network timeout, the connection is lost.
Client leaves the server
The server removes the connection id from this player slot.
0x0000 = means no error