/// <summary> /// Send updated MD5 data to the server /// </summary> /// <param name="updates">List of MD5 data to send</param> public virtual void SendMD5Updates(List <MD5Entry> updates) { MasterServer.Log("[{0}] Updating MD5 database to revision {1}", server, md5Manager.maxRevision); int index = 0; while (index < updates.Count) { int updatesInPacket = Math.Min(updates.Count - index, Protocol.MAX_MD5_UPDATES_PER_PACKET); OutboundPacket MD5UpdatePacket = new OutboundPacket((byte)MasterToServer.MD5Update); MD5UpdatePacket.Append((byte)updatesInPacket); for (int offset = 0; offset < updatesInPacket; offset++) { MD5UpdatePacket.Append(updates[index + offset].PackageGUID); MD5UpdatePacket.Append(updates[index + offset].PackageMD5); MD5UpdatePacket.Append(updates[index + offset].Revision); } ConnectionLog("SENDING MTS_MD5UPDATE COUNT={0}", updatesInPacket); Send(MD5UpdatePacket); index += updatesInPacket; } }
/// <summary> /// Client has queried the server list /// </summary> /// <param name="clientRequest"></param> private void HandleQuery(InboundPacket clientRequest) { // Query arrives as an array of QueryData structs QueryData[] queries = clientRequest.PopStructArray <QueryData>(); // Write query to log and notify master server for stats LogQuery(queries); // Get a filtered list of servers based on the queries which were receieved List <Server> servers = serverList.Query(queries); // Server count is the first reply following the query and tells the player how many servers to expect OutboundPacket serverCountPacket = new OutboundPacket(); serverCountPacket.Append(servers.Count); serverCountPacket.Append((byte)0x01); Send(serverCountPacket); // Send server list if any were found if (servers.Count > 0) { foreach (Server server in servers) { OutboundPacket serverListPacket = new OutboundPacket(); serverListPacket.AppendStruct <ServerListEntry>(server.ListEntry); Send(serverListPacket); } } }
/// <summary> /// Try to login with the specified details and get the response /// </summary> /// <param name="cdKey">CD key hash for login</param> /// <param name="saltedKey">Salted key hash for login</param> /// <param name="type">Remote client type (eg. CLIENT or SERVER)</param> /// <param name="version">Remote client version</param> /// <param name="locale">Remote client locale</param> /// <returns>Login response from the server</returns> public string Login(string cdKey, string saltedKey, string type, int version, string locale) { string response = Protocol.LOGIN_RESPONSE_DENIED; if (!connectionError) { try { OutboundPacket login = new OutboundPacket(); login.Append(cdKey).Append(saltedKey).Append(type).Append(version); if (type == Protocol.HOST_SERVER) { login.Append(-1); // stats enabled flag for server } login.Append((byte)0x04).Append(locale); Send(login); InboundPacket loginResponse = Receive(); response = loginResponse.PopString(); } catch { connectionError = true; } } Close(); return(response); }
public void Send(OutboundPacket packet) { Log.Verbose("Sending {rawPacket}", packet.Raw); Log.Debug("<- {packet}", packet); Connection?.Send(Encoding.ASCII.GetBytes(packet.Raw)); }
/// <summary> /// Send the specified outbound packet to the socket /// </summary> /// <param name="packet"></param> protected virtual void Send(OutboundPacket packet) { if (packet != null && packet.Type == OutboundPacketType.TCP && packet.Length > 0 && socket != null && socket.Connected) { socket.Send(packet); } }
/// <summary> /// Check INI option on the remote server, remote server will reply with STM_CheckOptionReply /// </summary> /// <param name="packageName">Name of package and group</param> /// <param name="variableName">Variable name</param> public virtual void CheckOption(string packageName, string variableName) { OutboundPacket CheckOptionPacket = new OutboundPacket((byte)MasterToServer.CheckOption); CheckOptionPacket.Append(packageName); CheckOptionPacket.Append(variableName); Send(CheckOptionPacket); }
/// <summary> /// Set the remote server's match ID /// </summary> /// <param name="MatchID">Match ID to set on the remote server</param> public virtual void SendMatchID(int MatchID) { ConnectionLog("ASSIGNING MATCH ID {0}", MatchID); OutboundPacket MatchIDPacket = new OutboundPacket((byte)MasterToServer.MatchID); MatchIDPacket.Append(MatchID); Send(MatchIDPacket); }
/// <summary> /// Set INI file option on the remote server /// </summary> /// <param name="packageName">Name of package and group</param> /// <param name="variableName">Variable name</param> /// <param name="value">Value to set</param> public virtual void SetOption(string packageName, string variableName, string value) { OutboundPacket SetOptionPacket = new OutboundPacket((byte)MasterToServer.UpdateOption); SetOptionPacket.Append(packageName); SetOptionPacket.Append(variableName); SetOptionPacket.Append(value); Send(SetOptionPacket); }
/// <summary> /// Receieved all heartbeats okay, send acknowledgment to server /// </summary> /// <param name="unknown">Not sure what this value is for, seems to always be 120</param> protected virtual void SendHeartbeatAcknowledgment(int unknown) { OutboundPacket HeartbeatExitPacket = new OutboundPacket(Protocol.HEARTBEAT_RESPONSE_CMD); HeartbeatExitPacket.Append(unknown); // Always 120 ? HeartbeatExitPacket.Append(server.QueryPort); HeartbeatExitPacket.Append(server.Port); HeartbeatExitPacket.Append(server.GamespyQueryPort); Send(HeartbeatExitPacket); }
/// <summary> /// Disconnect the specified player (eg. because of failed challenge or invalid CD key) /// </summary> /// <param name="clientAddress">Address of the player to disconnect, in IP:port notation</param> public virtual void DisconnectClient(string clientAddress) { ConnectionLog("SENDING MTS_CLIENTAUTHFAILED CLIENT={0}", clientAddress); MasterServer.Log("[{0}] Sending MTS_ClientAuthFailed to {1}", server, clientAddress); OutboundPacket DisconnectClientPacket = new OutboundPacket((byte)MasterToServer.ClientAuthFailed); DisconnectClientPacket.Append(clientAddress); Send(DisconnectClientPacket); }
/// <summary> /// Send a packet to a remote host /// </summary> /// <param name="udp">UDP client to use</param> /// <param name="packet">Packet to send</param> protected virtual void Send(UdpClient udp, OutboundPacket packet) { if (packet != null && packet.Type == OutboundPacketType.UDP && packet.Length > 0 && udp != null && remoteEndpoint != null) { udp.Send(packet, packet.FullLength, remoteEndpoint); } else { throw new ArgumentException("Cannot send a null, empty, or TCP packet on a UDP socket"); } }
/// <summary> /// Master server requests heartbeat blah with code blah /// </summary> /// <param name="type">Type of heartbeat to request</param> /// <param name="code">Heartbeat code to request</param> protected virtual void SendHeartbeatRequest(HeartbeatType type, int code) { if (State == ConnectionState.WaitingHello) { OutboundPacket HeartbeatPacket = new OutboundPacket(); HeartbeatPacket.Append(Protocol.HEARTBEAT_CMD); HeartbeatPacket.Append((byte)type); HeartbeatPacket.Append(code); waitingHearbeats.Add(type); Send(HeartbeatPacket); } }
/// <summary> /// Client has requested the MOTD /// </summary> /// <param name="clientRequest"></param> private void HandleMOTDRequest(InboundPacket clientRequest) { ConnectionLog("SENDING MOTD"); // Response packet contains the MOTD string OutboundPacket MOTD = new OutboundPacket(MasterServer.GetMOTD(locale, true)); // Send the MR_OptionalUpgrade value if this connection is valid but player is an outdated version if (outerConnection.Version < Protocol.OPTIONALUPGRADE_VERSION) { MOTD.Append(Protocol.OPTIONALUPGRADE_VERSION); } // Send the MOTD packet Send(MOTD); }
/// <summary> /// Challenge the specified player to return its CD key and the salted CD key /// </summary> /// <param name="clientAddress">Address of the player to challenge, in IP:port notation</param> public virtual void ChallengeClient(string clientAddress) { if (clientAddress != "" && clientAddress != "local") { ConnectionLog("SENDING MTS_CLIENTCHALLENGE CLIENT={0}", clientAddress); MasterServer.Log("[{0}] Sending MTS_ClientChallenge to {1}", server, clientAddress); ValidationContext clientValidationContext = cdKeyValidator.BeginValidation(clientAddress); validationContexts[clientAddress] = clientValidationContext; OutboundPacket ChallengeClientPacket = new OutboundPacket((byte)MasterToServer.ClientChallenge); ChallengeClientPacket.Append(clientAddress); ChallengeClientPacket.Append(clientValidationContext.Salt.ToString()); Send(ChallengeClientPacket); } else { MasterServer.Log("Unable to send MTS_ClientChallenge. Cannot challenge local player"); } }
/// <summary> /// Thread function where the query is actually executed. The call to Receive() is blocking and the /// separate timer thread takes care of the timeout behaviour (if specified) /// </summary> private void QueryThreadProc(object oQueryState) { UDPServerQueryState queryState = (UDPServerQueryState)oQueryState; try { // Send the query to the server OutboundPacket query = new OutboundPacket(true); query.Append((byte)queryState.QueryType); Send(queryState.QueryClient, query); // Wait for a response from the server UDPPacket queryResponse = Receive(queryState.QueryClient); // Stop the timeout timer queryState.End(); if (queryState.QueryResponse == UDPServerQueryResponse.None) { queryState.QueryResponse = UDPServerQueryResponse.Success; OnQueryFinished(queryState, queryResponse); } } catch (ThreadAbortException) { } catch (Exception) { if (queryState.QueryResponse != UDPServerQueryResponse.Timeout) { queryState.QueryResponse = UDPServerQueryResponse.Error; // Stop the timeout timer queryState.End(); OnQueryFinished(queryState, null); } } lock (activeQueryLock) { activeQueries.Remove(queryState); } }
/// <summary> /// Poke raw data down the connection, used for testing purposes /// </summary> /// <param name="command"></param> public void Poke(string[] command) { OutboundPacket ob = new OutboundPacket(); byte b = 0x00; for (int i = 1; i < command.Length; i++) { if (byte.TryParse(command[i], out b)) { ob.Append(b); } else { ob.Append(command[i]); } } ConnectionLog("POKE {0}", ob.Print()); Send(ob); }
/// <summary> /// Send the updated MS list to the remote client /// </summary> /// <returns>True if the packet was sent ok</returns> protected virtual bool SendMSList() { // Check whether we should send the MSLIST message on this port if (MSListEnabled) { // Calculate the number of entries we need to send int msListEntryCount = Math.Min(Math.Min(MasterServer.Settings.MSListServers.Length, MasterServer.Settings.MSListPorts.Length), MasterServer.Settings.MSListMaxServers); // Only send the list if there are entries to send if (msListEntryCount > 0) { ConnectionLog("{0} COUNT={1}", Protocol.LOGIN_RESPONSE_MSLIST, msListEntryCount); // Create MSLIST packet OutboundPacket msListPacket = new OutboundPacket(Protocol.LOGIN_RESPONSE_MSLIST); // Append server addresses msListPacket.Append((byte)msListEntryCount); for (int entry = 0; entry < msListEntryCount; entry++) { msListPacket.Append(MasterServer.Settings.MSListServers[entry]); } // Append server ports msListPacket.Append((byte)msListEntryCount); for (int entry = 0; entry < msListEntryCount; entry++) { msListPacket.Append((int)MasterServer.Settings.MSListPorts[entry]); } // Send packet to the remote host Send(msListPacket); return(true); } } return(false); }
/// <summary> /// Handle the connection /// </summary> protected virtual void Handle() { try { // Initialise validation context for this session validationContext = cdKeyValidator.BeginValidation("login"); // Log the new connection to the connection log ConnectionLog("ACCEPT LOCALPORT={0} SALT={1}", LocalPort, cdKeyValidator.GetSalt(validationContext)); // Send the challenge salt Send(cdKeyValidator.GetSalt(validationContext).ToString()); // Read back the authentication from the player InboundPacket login = Receive(); cdKey = login.PopString(); // Get the first MD5 which should be the CD key hash saltedCDKey = login.PopString(); // Get the second MD5 which should be the CD key plus salt hash type = login.PopString(); // Type of client eg. CLIENT or SERVER version = login.PopInt(); // Client's engine version // Write the login info to the connection log ConnectionLog("CONNECT MD5={0} SALTED={1} TYPE={2} VERSION={3}", cdKey, saltedCDKey, type, version); // Set values into the validation context validationContext.SetClientInfo(cdKey, saltedCDKey, type, version); // Check the CD key if (Validate(validationContext)) { if (version < Protocol.MIN_SUPPORTED_CLIENT_VERSION) { ConnectionLog(Protocol.LOGIN_RESPONSE_UPGRADE); MasterServer.Log("{0} at {1} rejected, outdated version: got {2}", type, (socket.RemoteEndPoint as IPEndPoint).Address.ToString(), version); // This is my best guess for how an UPGRADE packet should be structured, if it's wrong it seems to crash the client OutboundPacket UpgradePacket = new OutboundPacket(Protocol.LOGIN_RESPONSE_UPGRADE); UpgradePacket.Append(Protocol.MIN_SUPPORTED_CLIENT_VERSION); UpgradePacket.Append(0x00); // Send the UPGRADE response Send(UpgradePacket); } else { // Send MSLIST packet if enabled, if the MSLIST is sent successfully then close the // connection (SendMSList() returns true if the MSLIST was sent) if (!SendMSList()) { switch (type) { case Protocol.HOST_CLIENT: HandleClientConnection(login); break; case Protocol.HOST_SERVER: HandleServerConnection(login); break; default: HandleUnknownConnection(login); break; } } } } } catch (ThreadAbortException) { aborted = true; } catch (Exception ex) { ConnectionLog("EXCEPTION: {0}", ex.Message); } try { socket.Close(); } catch { } ConnectionLog("CLOSED"); ConnectionThreadManager.Remove(Thread.CurrentThread); ConnectionManager.DeRegister(this); }