-
-
Notifications
You must be signed in to change notification settings - Fork 99
Handshake Process
Firstly, I’d just like to clarify that by “handshake” I mean the veritable hailstorm of packets sent between the two instances of Rimworld - which I’ll refer to as the “client” and “server” for the joining player and the hosting player respectively, despite this not being entirely accurate - during the first few seconds of a connection, during which def databases are checked for mismatches, protocol versions are checked, and world data is exchanged.
The client initiates the connection by opening a socket to the server’s port and sending a Client_Protocol packet (ID 0), along with the current network protocol the client is using. The server receives this and checks it against its own protocol version. If the two don’t match, then the client is disconnected with the reason Protocol (1). Otherwise, the server sends back a Server_ModList (16) packet with its RimWorld version as a length-prefixed string, and the mods the server is running, in order, as a length-prefixed array of length-prefixed strings.
The client does not do any validation on either of these fields, assuming both the rimworld version and modlist is fine - though it does keep track of them - and in response sends the data needed to validate that the def databases match. This consists of a Client_Defs (1) packet, and along with this it sends the number of DefDatabases registered as well as, for each of these databases, what type it contains (e.g. ThingDef) as a length-prefixed string, followed by a 32-bit integer containing the number of Defs in that database, followed by a 32-bit integer containing the hash of the database.
If the client sends more than 512 database types, it’s disconnected with reason Generic (0)
The server checks each of these databases against its own and appends the result of each check to an array of statuses. If a database doesn’t exist on the server, but does on the client, then the status Not_Found (1) is used. If the count of defs mismatches, then the status Count_Diff (2) is used. If the hash mismatches, then Hash_Diff (3) is used. Otherwise, OK (0) is used. If any of these statuses is NOT Ok then the client is disconnected with reason Defs (2) and the status array.
Otherwise, the server sends Server_DefsOK (17) along with the game name and the client’s player id.
The client, upon receiving this packet, sets its internal status to Downloading (1), and sends a Client_Username (2) packet along with its chosen username. The server validates that this is not null or empty (in which case the packet is completely ignored) and then checks that it is between 3 and 15 characters long (inclusive) or kicks the client with reason UsernameLength (3). A handful of other checks are also performed, firstly that the client’s name is alphanumeric (without spaces) - if this check if failed the client is dropped with reason UsernameChars (4) - and that it’s not taken - if this is the case, then it’s kicked with reason UsernameAlreadyOnline (5).
At this point the server broadcasts a notification to each client, stating that a player has joined, and providing their username, and it also sends an updated player list.
Next, the server sets the player’s faction (currently only to the MP faction), and if the player is the only person on their faction online, then a notification is sent out that their faction is now online.
The server then sends the client a chunked Server_WorldData (18) packet, consisting of:
The player’s faction ID as a 32-bit int Another 32-bit int for the current tick A length-prefixed byte array of the gzip-compressed world data A length-prefixed array of map commands (consisting each of a map id, followed by a length-prefixed array of commands) And finally a length-prefixed array of gzip-compressed map data (consisting of a map id, then a length-prefixed byte array of the data itself).
It also sends a Server_PlayerList (19) packet consisting of the action id (List/0) as a byte, then a length-prefixed array of serialized player data.
The client decompresses this data and then invokes the standard rimworld load methods to load the save, followed by simulating the world at high-speed until the tick catches up with the current server-side one.
Once this is done, the client sends a Client_WorldReady (3) packet, at which point the server sets the player’s state to Playing, and the handshake is complete.