/// <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); }
/// <summary> /// Program Entry Point /// </summary> /// <param name="args"></param> static void Main(string[] args) { // Create our version string string version = String.Concat(Version.Major, ".", Version.Minor, ".", Version.Build); // Setup the console Console.Title = $"Battlefield2 Statistics Master Gamespy Emulator v{version}"; Console.WriteLine(@"__________ __ __ .__ _________ "); Console.WriteLine(@"\______ \_____ _/ |__/ |_| | ____ / _____/_____ ___.__."); Console.WriteLine(@" | | _/\__ \\ __\ __\ | _/ __ \ \_____ \\____ < | |"); Console.WriteLine(@" | | \ / __ \| | | | | |_\ ___/ / \ |_> >___ |"); Console.WriteLine(@" |______ /(____ /__| |__| |____/\___ >_______ / __// ____|"); Console.WriteLine(@" \/ \/ \/ \/|__| \/ "); Console.WriteLine(); Console.WriteLine("--------------------------------------------------"); Console.WriteLine("Battlefield 2 Master Gamespy Server Emulator v" + version); Console.WriteLine("Created for BF2Statistics.com by Wilson212"); Console.WriteLine(); // Start login servers try { // Make sure Logs dir is created if (!Directory.Exists(Path.Combine(RootPath, "Logs"))) { Directory.CreateDirectory(Path.Combine(RootPath, "Logs")); } // Wrap error log into a writer ErrorLog = new LogWriter(Path.Combine(RootPath, "Logs", "MasterServer_Error.log"), false); // Setup Exception Handle AppDomain.CurrentDomain.UnhandledException += ExceptionHandler.OnUnhandledException; Application.ThreadException += ExceptionHandler.OnThreadException; // Start the Gamespy Servers ServerManager.StartServers(); Console.WriteLine(); Console.Write("Cmd > "); } catch (Exception e) { // Display error Console.WriteLine(e.Message); Console.WriteLine(" *** An exception will be logged *** "); Console.WriteLine(); Console.Write("Press any key to close..."); // Create exception log and wait for key input ExceptionHandler.GenerateExceptionLog(e); Console.ReadLine(); return; } // Main program loop while (IsRunning) { CheckInput(); } // Shut login servers down Console.WriteLine("Shutting down local Gamespy sockets..."); ServerManager.Shutdown(); GeoIP.Exit(); }