/// <summary> /// Starts the Login Server listeners, and begins accepting new connections /// </summary> public static void StartServers() { // Make sure we arent already running! if (isRunning) { return; } try { // Create debug log LogWriter DebugLog = new LogWriter(Path.Combine(Program.RootPath, "Logs", "MasterServer_Debug.log"), true, 1); // Start the DB Connection Console.Write("Connecting to Mysql... "); using (MasterDatabase Database = new MasterDatabase()) { Console.Write("Success!" + Environment.NewLine); } // Create our end point to bind to int port = Config.GetType <int>("Settings", "MasterServerUdpPort"); IPAddress address = IPAddress.Parse(Config.GetValue("Settings", "ServerBindIp")); // Start Master Server, we write to console in the constructor because this // is actually 2 servers in 1 Console.Write("<MSTR> Binding to UDP port {0}... ", port); MstrServer = new MasterServer(new IPEndPoint(address, port), DebugLog); Console.Write("Success!" + Environment.NewLine); // Start the CDKey server port = Config.GetType <int>("Settings", "CDKeyServerUdpPort"); Console.Write("<CDKY> Binding to UDP port {0}... ", port); CDKeyServer = new CDKeyServer(new IPEndPoint(address, port), DebugLog); Console.Write("Success!" + Environment.NewLine); } catch { Console.Write("Failed!" + Environment.NewLine); throw; } // Let the client know we are ready for connections isRunning = true; }
/// <summary> /// When a server connects, it needs to be validated. Once that happens, this /// method is called, and it allows the server to bee seen in the Serverlist /// </summary> /// <param name="remote">The remote IP of the server</param> private void ValidateServer(IPEndPoint remote) { string key = String.Format("{0}:{1}", remote.Address, remote.Port); GameServer server; // try to fetch the existing server if (!Servers.TryGetValue(key, out server)) { // If the key exists, then what gives? if (Servers.ContainsKey(key) && !Servers.TryGetValue(key, out server)) { Program.ErrorLog.Write("NOTICE: [MasterServer.ValidateServer] Unable to fetch a connected server."); } return; } // Server is valid server.IsValidated = true; server.LastRefreshed = DateTime.Now; server.LastPing = DateTime.Now; // Update or add the new server if (Debugging) { DebugLog.Write("Adding Validated Server to Serverlist: " + key); } Servers.AddOrUpdate(key, server, (k, old) => { return(server); }); // Update the Dababase try { using (MasterDatabase Driver = new MasterDatabase()) { Driver.AddOrUpdateServer(server); } } catch (Exception e) { Program.ErrorLog.Write("ERROR: [MasterDatabase.AddOrUpdateServer] " + e.Message); } }
/// <summary> /// When a server connects, it needs to be validated. Once that happens, this /// method is called, and it allows the server to bee seen in the Serverlist /// </summary> /// <param name="remote">The remote IP of the server</param> private void ValidateServer(IPEndPoint remote) { string key = String.Format("{0}:{1}", remote.Address, remote.Port); GameServer server; // try to fetch the existing server, if its not here... we have bigger problems if (!Servers.TryGetValue(key, out server)) { Program.ErrorLog.Write("NOTICE: [MasterServer.ValidateServer] We encountered a strange error trying to fetch a connected server."); return; } // Server is valid server.IsValidated = true; server.LastRefreshed = DateTime.Now; server.LastPing = DateTime.Now; // Update or add the new server if (Debugging) { DebugLog.Write("Adding Validated Server to Serverlist: " + key); } Servers.AddOrUpdate(key, server, (k, old) => { return(server); }); // Update the Dababase try { using (MasterDatabase Driver = new MasterDatabase()) { Driver.AddOrUpdateServer(server); } } catch (Exception e) { Program.ErrorLog.Write("ERROR: [MasterDatabase.AddOrUpdateServer] " + e.Message); } }
/// <summary> /// Executed every 5 seconds or so... Removes all servers that haven't /// reported in awhile /// </summary> protected void CheckServers() { // Create a list of servers to update in the database List <GameServer> ServersToRemove = new List <GameServer>(); // Remove servers that havent talked to us in awhile from the server list foreach (string key in Servers.Keys) { GameServer value; if (Servers.TryGetValue(key, out value)) { if (value.LastPing < DateTime.Now - TimeSpan.FromSeconds(ServerTTL)) { if (Debugging) { DebugLog.Write("Removing Server for Expired Ping: " + key); } if (Servers.TryRemove(key, out value)) { ServersToRemove.Add(value); } else { Program.ErrorLog.Write("ERROR: [MasterServer.CheckServers] Unable to remove server from server list: " + key); } } } } // If we have no servers to update, return if (ServersToRemove.Count == 0) { return; } // Update servers in database try { // Wrap this all in a database transaction, as this will speed // things up alot if there are alot of rows to update using (MasterDatabase Driver = new MasterDatabase()) using (DbTransaction Transaction = Driver.BeginTransaction()) { try { foreach (GameServer server in ServersToRemove) { Driver.UpdateServerOffline(server); } Transaction.Commit(); } catch { Transaction.Rollback(); throw; } } } catch (Exception e) { Program.ErrorLog.Write("ERROR: [MasterDatabase.UpdateServerStatus] Unable to update servers status: " + e.Message); } }
/// <summary> /// Executed every 60 seconds... Every 3rd ping, the BF2 server sends a full list /// of data that describes its current state, and this method is used to parse that /// data, and update the server in the Servers list /// </summary> /// <param name="remote">The servers remote address</param> /// <param name="data">The data we must parse, sent by the server</param> /// <returns>Returns whether or not the server needs to be validated, so it can be seen in the Server Browser</returns> private bool ParseServerDetails(IPEndPoint remote, byte[] data) { // Format key string key = String.Format("{0}:{1}", remote.Address, remote.Port); // split by 000 (info/player separator) and 002 (players/teams separator) // the players/teams separator is really 00, but because 00 may also be used elsewhere (an empty value for example), we hardcode it to 002 // the 2 is the size of the teams, for BF2 this is always 2. string receivedData = Encoding.UTF8.GetString(data); string[] sections = receivedData.Split(new string[] { "\x00\x00\x00", "\x00\x00\x02" }, StringSplitOptions.None); if (sections.Length != 3 && !receivedData.EndsWith("\x00\x00")) { if (Debugging) { DebugLog.Write("Invalid Server Data Received From {0} :: {1}", key, sections[0]); } return(true); // true means we don't send back a response } // We only care about the server section string serverVars = sections[0]; string[] serverVarsSplit = serverVars.Split(new string[] { "\x00" }, StringSplitOptions.None); if (Debugging) { DebugLog.Write("Server Data Received From {0}", key); for (int i = 0; i < sections.Length; i++) { DebugLog.Write(" DataString {0}: {1}", i, sections[i]); } } // Start a new Server Object GameServer server = new GameServer(remote); List <Dictionary <string, object> > Rows = null; // set the country based off ip address if its IPv4 server.country = (remote.Address.AddressFamily == AddressFamily.InterNetwork) ? GeoIP.GetCountryCode(remote.Address).ToUpperInvariant() : "??"; // Try and Fetch server and provider from the database try { using (MasterDatabase Driver = new MasterDatabase()) { string q = "SELECT s.*, p.authorized, p.plasma FROM server AS s JOIN stats_provider AS p ON s.provider_id = p.id WHERE ip=@P0 AND queryport=@P1"; Rows = Driver.Query(q, remote.Address, remote.Port); } } catch (Exception e) { Program.ErrorLog.Write("WARNING: [MasterServer.ParseServerDetails] " + e.Message); } // Set server vars for (int i = 0; i < serverVarsSplit.Length - 1; i += 2) { // Fetch the property PropertyInfo property = typeof(GameServer).GetProperty(serverVarsSplit[i]); if (property == null) { continue; } else if (property.Name == "hostname") { // strip consecutive whitespace from hostname property.SetValue(server, Regex.Replace(serverVarsSplit[i + 1], @"\s+", " ").Trim(), null); } else if (property.Name == "bf2_plasma") { // Does the server exist in the database? if (Rows != null && Rows.Count > 0) { // For some damn reason, the driver is returning a bool instead of an integer? if (Rows[0]["plasma"] is bool) { property.SetValue(server, Rows[0]["plasma"], null); } else { property.SetValue(server, Int32.Parse(Rows[0]["plasma"].ToString()) > 0, null); } } else { property.SetValue(server, false, null); } } else if (property.Name == "bf2_ranked") { // Does the server exist in the database? if (Rows != null && Rows.Count > 0) { // For some damn reason, the driver is returning a bool instead of an integer? if (Rows[0]["authorized"] is bool) { property.SetValue(server, Rows[0]["authorized"], null); } else { property.SetValue(server, Int32.Parse(Rows[0]["authorized"].ToString()) > 0, null); } } else { property.SetValue(server, false, null); } } else if (property.Name == "bf2_pure") { // we're always a pure server property.SetValue(server, true, null); } else if (property.PropertyType == typeof(Boolean)) { // parse string to bool (values come in as 1 or 0) int value; if (Int32.TryParse(serverVarsSplit[i + 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) { property.SetValue(server, value != 0, null); } } else if (property.PropertyType == typeof(Int32)) { // parse string to int int value; if (Int32.TryParse(serverVarsSplit[i + 1], NumberStyles.Integer, CultureInfo.InvariantCulture, out value)) { property.SetValue(server, value, null); } } else if (property.PropertyType == typeof(Double)) { // parse string to double double value; if (Double.TryParse(serverVarsSplit[i + 1], NumberStyles.Float, CultureInfo.InvariantCulture, out value)) { property.SetValue(server, value, null); } } else if (property.PropertyType == typeof(String)) { // parse string to string property.SetValue(server, serverVarsSplit[i + 1], null); } } // you've got to have all these properties in order for your server to be valid if (!String.IsNullOrWhiteSpace(server.hostname) && !String.IsNullOrWhiteSpace(server.gamevariant) && !String.IsNullOrWhiteSpace(server.gamever) && !String.IsNullOrWhiteSpace(server.gametype) && !String.IsNullOrWhiteSpace(server.mapname) && !String.IsNullOrWhiteSpace(server.gamename) && server.gamename.Equals("battlefield2", StringComparison.InvariantCultureIgnoreCase) && server.hostport > 1024 && server.hostport <= UInt16.MaxValue && server.maxplayers > 0) { // Determine if we need to send a challenge key to the server for validation bool IsValidated = Servers.ContainsKey(key) && Servers[key].IsValidated; if (Debugging) { DebugLog.Write("Server Data Parsed Successfully... Needs Validated: " + ((IsValidated) ? "false" : "true")); } // Add / Update Server server.IsValidated = IsValidated; server.LastPing = DateTime.Now; server.LastRefreshed = DateTime.Now; Servers.AddOrUpdate(key, server, (k, old) => { return(server); }); // Tell the requester if we are good to go return(IsValidated); } // If we are here, the server information is invalid. Return true to ignore server return(true); }