/// <summary> /// Queue initial heartbeats to active servers /// </summary> public void QueueInitialHeartbeats() { DateTime Now = DateTime.UtcNow; uint HeartbeatsStarted = 0; lock (ActiveServerTable) { foreach (var Pair in ActiveServerTable) { NWGameServer Server = Pair.Value; lock (Server) { if (!Server.Online) { continue; } Server.InitialHeartbeat = true; Server.StartHeartbeat(); HeartbeatsStarted += 1; } } } Logger.Log(LogLevel.Normal, "NWServerTracker.QueueInitialHeartbeats(): Queued {0} initial server heartbeat requests.", HeartbeatsStarted); PendingGameServersSweepTimer.Start(); ScavengerSweepTimer.Start(); BlacklistSweepTimer.Start(); }
/// <summary> /// Request a heartbeat on behalf of a server object. /// </summary> /// <param name="Server">Supplies the server object instance.</param> /// <returns>True if future heartbeats are enabled.</returns> public bool RequestHeartbeat(NWGameServer Server) { // // Check if heartbeats are enabled. If so, take the heartbeat lock // and request the heartbeat. // lock (HeartbeatLock) { if (!HeartbeatsEnabled) { return(false); } MasterServer.RefreshServerStatus(Server.ServerAddress); } return(true); }
/// <summary> /// Attempt to look up a game server by address, creating the server if /// requested (if it did not exist). /// </summary> /// <param name="ServerAddress">Supplies the server address to look up /// in the active server table.</param> /// <param name="Create">Supplies true if the server instance should be /// created if it did not exist.</param> /// <returns>The active server instance, else null if no server /// satisfied the given criteria.</returns> public NWGameServer LookupServerByAddress(IPEndPoint ServerAddress, bool Create = true) { NWGameServer Server; lock (ActiveServerTable) { if (!ActiveServerTable.TryGetValue(ServerAddress, out Server)) { Server = null; } if ((Create != false) && (Server == null)) { Server = new NWGameServer(MasterServer, ServerAddress); Server.Load(); ActiveServerTable.Add(ServerAddress, Server); } } return(Server); }
/// <summary> /// This method attempts to determine whether a different server, homed /// at the same address, is a "NAT duplicate" of the current server. /// /// Some broken NAT devices will pick an ephemeral source port for the /// master heartbeat ping, and will (for a short time) even respond on /// this port, even though they also respond on the declared server /// internal port, which is the canonical address to select. /// /// However, it is legitimate for two different servers to reside at /// different ports on the same IP address. In order to distinguish /// between these conditions, a test is made as to whether the data set /// returned by a BNXR message from both servers is identical, for the /// configuration settings. If so, then a NAT duplicate is declared, /// otherwise both servers are considered to be legitimately distinct. /// /// In the condition where a NAT duplicate is detected, the internal /// advertised address of the server is assumed to be the canonical /// address. In such a case, the InternalServer argument corresponds /// to the canonical server, and the this pointer corresponds to the /// NAT duplicate. The NAT duplicate is marked offline and its /// heartbeat is then descheduled. /// </summary> /// <param name="InternalServer">Supplies the potential canonical half /// of a NAT duplicate server, to be compared against.</param> /// <returns>True if the current server was marked offline as a NAT /// duplicate.</returns> public bool CheckForNATDuplicate(NWGameServer InternalServer) { lock (this) { lock (InternalServer) { if ((ModuleName == InternalServer.ModuleName) && (PrivateServer == InternalServer.PrivateServer) && (MinimumLevel == InternalServer.MinimumLevel) && (MaximumLevel == InternalServer.MaximumLevel) && (MaximumPlayerCount == InternalServer.MaximumPlayerCount) && (LocalVault == InternalServer.LocalVault) && (PVPLevel == InternalServer.PVPLevel) && (PlayerPause == InternalServer.PlayerPause) && (OnePartyOnly == InternalServer.OnePartyOnly) && (ELCEnforced == InternalServer.ELCEnforced) && (ILREnforced == InternalServer.ILREnforced) && (ExpansionsMask == InternalServer.ExpansionsMask)) { NATDuplicateTick = (uint)Environment.TickCount; if (Online) { Online = false; StopHeartbeat(); } Save(); return(true); } } } return(false); }
/// <summary> /// Attempt to load data from the database. The server is assumed to /// be either locked or not yet published. /// </summary> public void Load() { string Query = String.Format( @"SELECT `game_server_id`, `expansions_mask`, `build_number`, `module_name`, `server_name`, `active_player_count`, `maximum_player_count`, `local_vault`, `last_heartbeat`, `server_address`, `online`, `private_server`, `module_description`, `module_url`, `game_type`, `minimum_level`, `maximum_level`, `pvp_level`, `player_pause`, `one_party_only`, `elc_enforced`, `ilr_enforced`, `pwc_url`, `server_description` FROM `game_servers` WHERE `product_id` = {0} AND `server_address` = '{1}'", MasterServer.ProductID, MySqlHelper.EscapeString(ServerAddress.ToString())); try { using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { if (!Reader.Read()) { return; } DatabaseId = Reader.GetUInt32(0); NWGameServer Server = new NWGameServer(MasterServer, ServerAddress); ExpansionsMask = (Byte)Reader.GetUInt32(1); BuildNumber = (UInt16)Reader.GetUInt32(2); ModuleName = Reader.GetString(3); ServerName = Reader.GetString(4); MaximumPlayerCount = Reader.GetUInt32(6); LocalVault = Reader.GetBoolean(7); PrivateServer = Reader.GetBoolean(11); ModuleDescription = Reader.GetString(12); ModuleUrl = Reader.GetString(13); GameType = Reader.GetUInt32(14); MinimumLevel = Reader.GetUInt32(15); MaximumLevel = Reader.GetUInt32(16); PVPLevel = Reader.GetUInt32(17); Server.PlayerPause = Reader.GetBoolean(18); Server.OnePartyOnly = Reader.GetBoolean(19); Server.ELCEnforced = Reader.GetBoolean(20); Server.ILREnforced = Reader.GetBoolean(21); PWCUrl = Reader.GetString(22); ServerDescription = Reader.GetString(23); if (Online) { StartHeartbeat(); } } } catch (Exception e) { Logger.Log(LogLevel.Error, "NWMasterServer.Load(): Failed to load server {0}: Exception: {1}", ServerAddress, e); } }
/// <summary> /// This timer callback runs when the scavenge sweep timer elapses. /// Its purpose is to cycle through offline game_servers records in the /// database, which have had a heartbeat more recent than /// ScavengerTimeSpan since the current time. Such servers are then /// re-pinged in a low frequency interval so as to allow servers that /// had gone offline for a long period of time (but had been returned /// to service afterwards) to be automatically re-listed eventually. /// </summary> /// <param name="sender">Unused.</param> /// <param name="e">Unused.</param> private void ScavengerSweepTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { bool Processed = false; DateTime Now = DateTime.UtcNow; string Query = String.Format( @"SELECT `game_server_id`, `server_address` FROM `game_servers` WHERE `product_id` = {0} AND `game_server_id` > {1} AND `online` = false AND `last_heartbeat` >= '{2}' GROUP BY `game_server_id` ORDER BY `game_server_id` LIMIT 50", MasterServer.ProductID, ScavengeServerId, MasterServer.DateToSQLDate(Now.Subtract(ScavengerTimeSpan))); // // Examine each server record returned and, if the server is // still offline, enqueue a ping to it. Advance the iterator // to the next server as each server is processed. // using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { while (Reader.Read()) { uint ServerId = Reader.GetUInt32(0); IPEndPoint ServerAddress = ConvertServerHostnameToIPEndPoint(Reader.GetString(1)); if (ServerId > ScavengeServerId) { ScavengeServerId = ServerId; } Processed = true; if (ServerAddress == null) { continue; } NWGameServer Server = LookupServerByAddress(ServerAddress, false); if (Server == null || Server.Online == false) { MasterServer.SendServerInfoRequest(ServerAddress); } } } // // If no servers were processed, then the end of the iterator // list must have been reached. Re-start the sweep cycle at // the beginning next time. // if (Processed == false) { ScavengeServerId = 0; } } catch (Exception ex) { Logger.Log(LogLevel.Error, "NWServerTracker.ScavengeSweepTimer_Elapsed(): Excepton processing scavenge server list: {0}", ex); } lock (HeartbeatLock) { if (!HeartbeatsEnabled) { return; } ScavengerSweepTimer.Start(); } }
/// <summary> /// This timer callback runs when the pending game servers sweep timer /// elapses. It checks whether there is a pending query queue, and if /// so, flushes it as appropriate. /// </summary> /// <param name="sender">Unused.</param> /// <param name="e">Unused.</param> private void PendingGameServersSweepTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e) { try { uint HighestPendingServerId = 0; bool DataReturned = false; string Query = String.Format( @"SELECT `pending_game_server_id`, `server_address` FROM `pending_game_servers` WHERE `product_id` = {0} GROUP BY `pending_game_server_id` ORDER BY `pending_game_server_id` LIMIT 100 ", MasterServer.ProductID); using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { while (Reader.Read()) { uint PendingServerId = Reader.GetUInt32(0); string Hostname = Reader.GetString(1); IPEndPoint ServerAddress; if (PendingServerId > HighestPendingServerId) { HighestPendingServerId = PendingServerId; } if (DataReturned == false) { DataReturned = true; } ServerAddress = ConvertServerHostnameToIPEndPoint(Hostname); if (ServerAddress == null) { Logger.Log(LogLevel.Error, "NWServerTracker.PendingGameServersSweepTimer_Elapsed(): Server {0} has invalid hostname '{1}'.", PendingServerId, Hostname); continue; } NWGameServer Server = LookupServerByAddress(ServerAddress, false); if (Server == null || Server.Online == false) { MasterServer.SendServerInfoRequest(ServerAddress); } } } if (DataReturned) { // // Remove records that have already been processed. // MasterServer.ExecuteQueryNoReader(String.Format( @"DELETE FROM `pending_game_servers` WHERE `product_id` = {0} AND `pending_game_server_id` < {1}", MasterServer.ProductID, HighestPendingServerId + 1)); } } catch (Exception ex) { Logger.Log(LogLevel.Error, "NWServerTracker.PendingGameServersSweepTimer_Elapsed(): Exception processing pending servers list: {0}.", ex); } lock (HeartbeatLock) { if (!HeartbeatsEnabled) { return; } PendingGameServersSweepTimer.Start(); } }
/// <summary> /// Prepare the database, creating tables (if they did not already /// exist). /// </summary> private void InitializeDatabase() { uint ServersAdded; if (String.IsNullOrEmpty(MasterServer.ConnectionString)) { return; } try { MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `game_servers` ( `game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `expansions_mask` int(10) UNSIGNED NOT NULL, `build_number` int(10) UNSIGNED NOT NULL, `module_name` varchar(32) NOT NULL, `server_name` varchar(256) NOT NULL, `active_player_count` int(10) UNSIGNED NOT NULL, `maximum_player_count` int(10) UNSIGNED NOT NULL, `local_vault` bool NOT NULL, `last_heartbeat` datetime NOT NULL, `server_address` varchar(128) NOT NULL, `online` bool NOT NULL, `private_server` bool NOT NULL, `module_description` varchar(256) NOT NULL, `module_url` varchar(256) NOT NULL, `game_type` int(10) UNSIGNED NOT NULL, `minimum_level` int(10) UNSIGNED NOT NULL, `maximum_level` int(10) UNSIGNED NOT NULL, `pvp_level` int(10) UNSIGNED NOT NULL, `player_pause` bool NOT NULL, `one_party_only` bool NOT NULL, `elc_enforced` bool NOT NULL, `ilr_enforced` bool NOT NULL, `pwc_url` varchar(256) NOT NULL, `server_description` varchar(256) NOT NULL, `game_server_group_id` int(10) UNSIGNED, PRIMARY KEY (`game_server_id`), UNIQUE KEY (`product_id`, `server_address`), INDEX (`product_id`, `online`), INDEX (`product_id`, `online`, `server_name`), INDEX (`product_id`, `online`, `module_name`), INDEX (`product_id`, `online`, `game_type`), INDEX (`product_id`, `online`, `game_server_group_id`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `game_server_groups` ( `game_server_group_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `group_name` varchar(128) NOT NULL, `global_user_counts` bool NOT NULL, PRIMARY KEY (`game_server_group_id`), UNIQUE KEY (`product_id`, `group_name`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `pending_game_servers` ( `pending_game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `server_address` varchar(128) NOT NULL, PRIMARY KEY (`pending_game_server_id`), UNIQUE KEY (`product_id`, `server_address`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `client_extension_update` ( `update_id` int(10) UNSIGNED NOT NULL, `product_id` int(10) UNSIGNED NOT NULL, `update_message` varchar(4096) NOT NULL, `update_url` varchar(4096) NOT NULL, `update_info_url` varchar(4096) NOT NULL, `update_version` varchar(128) NOT NULL, `update_motd` varchar(4096) NOT NULL, PRIMARY KEY (`update_id`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `stat_counters` ( `stat_counter_name` varchar(64) NOT NULL, `stat_counter_value` int(10) UNSIGNED NOT NULL, `stat_counter_last_update` datetime NOT NULL, PRIMARY KEY (`stat_counter_name`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `blacklist_entries` ( `blacklist_entry_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `blacklist_entry_type` int(10) UNSIGNED NOT NULL, `blacklist_entry_match` varchar(128) NOT NULL, PRIMARY KEY (`blacklist_entry_id`) )"); string Query = String.Format( @"SELECT `game_server_id`, `expansions_mask`, `build_number`, `module_name`, `server_name`, `active_player_count`, `maximum_player_count`, `local_vault`, `last_heartbeat`, `server_address`, `online`, `private_server`, `module_description`, `module_url`, `game_type`, `minimum_level`, `maximum_level`, `pvp_level`, `player_pause`, `one_party_only`, `elc_enforced`, `ilr_enforced`, `pwc_url`, `server_description` FROM `game_servers` WHERE `product_id` = {0} AND `online` = true", MasterServer.ProductID); RefreshBlacklist(); ServersAdded = 0; using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { while (Reader.Read()) { uint ServerId = Reader.GetUInt32(0); string Hostname = Reader.GetString(9); int i = Hostname.IndexOf(':'); IPEndPoint ServerAddress; if (i == -1) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Server {0} has invalid hostname '{1}'.", ServerId, Hostname); continue; } try { ServerAddress = new IPEndPoint( IPAddress.Parse(Hostname.Substring(0, i)), Convert.ToInt32(Hostname.Substring(i + 1))); } catch (Exception e) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Error initializing hostname {0} for server {1}: Exception: {2}", Hostname, ServerId, e); continue; } // // Query for whether the server is blacklisted and do // not re-list it if so. // BlacklistLookup Lookup = new BlacklistLookup(); Lookup.ServerAddress = ServerAddress; Lookup.ModuleName = Reader.GetString(3); if (IsBlacklisted(Lookup)) { continue; } NWGameServer Server = new NWGameServer(MasterServer, ServerAddress); Server.DatabaseId = ServerId; Server.ExpansionsMask = (Byte)Reader.GetUInt32(1); Server.BuildNumber = (UInt16)Reader.GetUInt32(2); Server.ModuleName = Reader.GetString(3); Server.ServerName = Reader.GetString(4); Server.ActivePlayerCount = Reader.GetUInt32(5); Server.MaximumPlayerCount = Reader.GetUInt32(6); Server.LocalVault = Reader.GetBoolean(7); Server.LastHeartbeat = Reader.GetDateTime(8); Server.Online = Reader.GetBoolean(10); Server.PrivateServer = Reader.GetBoolean(11); Server.ModuleDescription = Reader.GetString(12); Server.ModuleUrl = Reader.GetString(13); Server.GameType = Reader.GetUInt32(14); Server.MinimumLevel = Reader.GetUInt32(15); Server.MaximumLevel = Reader.GetUInt32(16); Server.PVPLevel = Reader.GetUInt32(17); Server.PlayerPause = Reader.GetBoolean(18); Server.OnePartyOnly = Reader.GetBoolean(19); Server.ELCEnforced = Reader.GetBoolean(20); Server.ILREnforced = Reader.GetBoolean(21); Server.PWCUrl = Reader.GetString(22); Server.ServerDescription = Reader.GetString(23); lock (ActiveServerTable) { ActiveServerTable.Add(ServerAddress, Server); } ServersAdded += 1; } } Logger.Log(LogLevel.Normal, "NWServerTracker.InitializeDatabase(): Read {0} active servers from database.", ServersAdded); } catch (Exception e) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Exception: {0}", e); MasterServer.Stop(); } }
/// <summary> /// Attempt to load data from the database. The server is assumed to /// be either locked or not yet published. /// </summary> public void Load() { string Query = String.Format( @"SELECT `game_server_id`, `expansions_mask`, `build_number`, `module_name`, `server_name`, `active_player_count`, `maximum_player_count`, `local_vault`, `last_heartbeat`, `server_address`, `online`, `private_server`, `module_description`, `module_url`, `game_type`, `minimum_level`, `maximum_level`, `pvp_level`, `player_pause`, `one_party_only`, `elc_enforced`, `ilr_enforced`, `pwc_url`, `server_description` FROM `game_servers` WHERE `product_id` = {0} AND `server_address` = '{1}'", MasterServer.ProductID, MySqlHelper.EscapeString(ServerAddress.ToString())); try { using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { if (!Reader.Read()) return; DatabaseId = Reader.GetUInt32(0); NWGameServer Server = new NWGameServer(MasterServer, ServerAddress); ExpansionsMask = (Byte)Reader.GetUInt32(1); BuildNumber = (UInt16)Reader.GetUInt32(2); ModuleName = Reader.GetString(3); ServerName = Reader.GetString(4); MaximumPlayerCount = Reader.GetUInt32(6); LocalVault = Reader.GetBoolean(7); PrivateServer = Reader.GetBoolean(11); ModuleDescription = Reader.GetString(12); ModuleUrl = Reader.GetString(13); GameType = Reader.GetUInt32(14); MinimumLevel = Reader.GetUInt32(15); MaximumLevel = Reader.GetUInt32(16); PVPLevel = Reader.GetUInt32(17); Server.PlayerPause = Reader.GetBoolean(18); Server.OnePartyOnly = Reader.GetBoolean(19); Server.ELCEnforced = Reader.GetBoolean(20); Server.ILREnforced = Reader.GetBoolean(21); PWCUrl = Reader.GetString(22); ServerDescription = Reader.GetString(23); if (Online) StartHeartbeat(); } } catch (Exception e) { Logger.Log(LogLevel.Error, "NWMasterServer.Load(): Failed to load server {0}: Exception: {1}", ServerAddress, e); } }
/// <summary> /// This method attempts to determine whether a different server, homed /// at the same address, is a "NAT duplicate" of the current server. /// /// Some broken NAT devices will pick an ephemeral source port for the /// master heartbeat ping, and will (for a short time) even respond on /// this port, even though they also respond on the declared server /// internal port, which is the canonical address to select. /// /// However, it is legitimate for two different servers to reside at /// different ports on the same IP address. In order to distinguish /// between these conditions, a test is made as to whether the data set /// returned by a BNXR message from both servers is identical, for the /// configuration settings. If so, then a NAT duplicate is declared, /// otherwise both servers are considered to be legitimately distinct. /// /// In the condition where a NAT duplicate is detected, the internal /// advertised address of the server is assumed to be the canonical /// address. In such a case, the InternalServer argument corresponds /// to the canonical server, and the this pointer corresponds to the /// NAT duplicate. The NAT duplicate is marked offline and its /// heartbeat is then descheduled. /// </summary> /// <param name="InternalServer">Supplies the potential canonical half /// of a NAT duplicate server, to be compared against.</param> /// <returns>True if the current server was marked offline as a NAT /// duplicate.</returns> public bool CheckForNATDuplicate(NWGameServer InternalServer) { lock (this) { lock (InternalServer) { if ((ModuleName == InternalServer.ModuleName) && (PrivateServer == InternalServer.PrivateServer) && (MinimumLevel == InternalServer.MinimumLevel) && (MaximumLevel == InternalServer.MaximumLevel) && (MaximumPlayerCount == InternalServer.MaximumPlayerCount) && (LocalVault == InternalServer.LocalVault) && (PVPLevel == InternalServer.PVPLevel) && (PlayerPause == InternalServer.PlayerPause) && (OnePartyOnly == InternalServer.OnePartyOnly) && (ELCEnforced == InternalServer.ELCEnforced) && (ILREnforced == InternalServer.ILREnforced) && (ExpansionsMask == InternalServer.ExpansionsMask)) { NATDuplicateTick = (uint)Environment.TickCount; if (Online) { Online = false; StopHeartbeat(); } Save(); return true; } } } return false; }
/// <summary> /// Prepare the database, creating tables (if they did not already /// exist). /// </summary> private void InitializeDatabase() { uint ServersAdded; if (String.IsNullOrEmpty(MasterServer.ConnectionString)) return; try { MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `game_servers` ( `game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `expansions_mask` int(10) UNSIGNED NOT NULL, `build_number` int(10) UNSIGNED NOT NULL, `module_name` varchar(32) NOT NULL, `server_name` varchar(256) NOT NULL, `active_player_count` int(10) UNSIGNED NOT NULL, `maximum_player_count` int(10) UNSIGNED NOT NULL, `local_vault` bool NOT NULL, `last_heartbeat` datetime NOT NULL, `server_address` varchar(128) NOT NULL, `online` bool NOT NULL, `private_server` bool NOT NULL, `module_description` varchar(256) NOT NULL, `module_url` varchar(256) NOT NULL, `game_type` int(10) UNSIGNED NOT NULL, `minimum_level` int(10) UNSIGNED NOT NULL, `maximum_level` int(10) UNSIGNED NOT NULL, `pvp_level` int(10) UNSIGNED NOT NULL, `player_pause` bool NOT NULL, `one_party_only` bool NOT NULL, `elc_enforced` bool NOT NULL, `ilr_enforced` bool NOT NULL, `pwc_url` varchar(256) NOT NULL, `server_description` varchar(256) NOT NULL, `game_server_group_id` int(10) UNSIGNED, PRIMARY KEY (`game_server_id`), UNIQUE KEY (`product_id`, `server_address`), INDEX (`product_id`, `online`), INDEX (`product_id`, `online`, `server_name`), INDEX (`product_id`, `online`, `module_name`), INDEX (`product_id`, `online`, `game_type`), INDEX (`product_id`, `online`, `game_server_group_id`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `game_server_groups` ( `game_server_group_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `group_name` varchar(128) NOT NULL, `global_user_counts` bool NOT NULL, PRIMARY KEY (`game_server_group_id`), UNIQUE KEY (`product_id`, `group_name`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `pending_game_servers` ( `pending_game_server_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `server_address` varchar(128) NOT NULL, PRIMARY KEY (`pending_game_server_id`), UNIQUE KEY (`product_id`, `server_address`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `client_extension_update` ( `update_id` int(10) UNSIGNED NOT NULL, `product_id` int(10) UNSIGNED NOT NULL, `update_message` varchar(4096) NOT NULL, `update_url` varchar(4096) NOT NULL, `update_info_url` varchar(4096) NOT NULL, `update_version` varchar(128) NOT NULL, `update_motd` varchar(4096) NOT NULL, PRIMARY KEY (`update_id`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `stat_counters` ( `stat_counter_name` varchar(64) NOT NULL, `stat_counter_value` int(10) UNSIGNED NOT NULL, `stat_counter_last_update` datetime NOT NULL, PRIMARY KEY (`stat_counter_name`) )"); MasterServer.ExecuteQueryNoReader( @"CREATE TABLE IF NOT EXISTS `blacklist_entries` ( `blacklist_entry_id` int(10) UNSIGNED NOT NULL AUTO_INCREMENT, `product_id` int(10) UNSIGNED NOT NULL, `blacklist_entry_type` int(10) UNSIGNED NOT NULL, `blacklist_entry_match` varchar(128) NOT NULL, PRIMARY KEY (`blacklist_entry_id`) )"); string Query = String.Format( @"SELECT `game_server_id`, `expansions_mask`, `build_number`, `module_name`, `server_name`, `active_player_count`, `maximum_player_count`, `local_vault`, `last_heartbeat`, `server_address`, `online`, `private_server`, `module_description`, `module_url`, `game_type`, `minimum_level`, `maximum_level`, `pvp_level`, `player_pause`, `one_party_only`, `elc_enforced`, `ilr_enforced`, `pwc_url`, `server_description` FROM `game_servers` WHERE `product_id` = {0} AND `online` = true", MasterServer.ProductID); RefreshBlacklist(); ServersAdded = 0; using (MySqlDataReader Reader = MasterServer.ExecuteQuery(Query)) { while (Reader.Read()) { uint ServerId = Reader.GetUInt32(0); string Hostname = Reader.GetString(9); int i = Hostname.IndexOf(':'); IPEndPoint ServerAddress; if (i == -1) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Server {0} has invalid hostname '{1}'.", ServerId, Hostname); continue; } try { ServerAddress = new IPEndPoint( IPAddress.Parse(Hostname.Substring(0, i)), Convert.ToInt32(Hostname.Substring(i + 1))); } catch (Exception e) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Error initializing hostname {0} for server {1}: Exception: {2}", Hostname, ServerId, e); continue; } // // Query for whether the server is blacklisted and do // not re-list it if so. // BlacklistLookup Lookup = new BlacklistLookup(); Lookup.ServerAddress = ServerAddress; Lookup.ModuleName = Reader.GetString(3); if (IsBlacklisted(Lookup)) continue; NWGameServer Server = new NWGameServer(MasterServer, ServerAddress); Server.DatabaseId = ServerId; Server.ExpansionsMask = (Byte)Reader.GetUInt32(1); Server.BuildNumber = (UInt16)Reader.GetUInt32(2); Server.ModuleName = Reader.GetString(3); Server.ServerName = Reader.GetString(4); Server.ActivePlayerCount = Reader.GetUInt32(5); Server.MaximumPlayerCount = Reader.GetUInt32(6); Server.LocalVault = Reader.GetBoolean(7); Server.LastHeartbeat = Reader.GetDateTime(8); Server.Online = Reader.GetBoolean(10); Server.PrivateServer = Reader.GetBoolean(11); Server.ModuleDescription = Reader.GetString(12); Server.ModuleUrl = Reader.GetString(13); Server.GameType = Reader.GetUInt32(14); Server.MinimumLevel = Reader.GetUInt32(15); Server.MaximumLevel = Reader.GetUInt32(16); Server.PVPLevel = Reader.GetUInt32(17); Server.PlayerPause = Reader.GetBoolean(18); Server.OnePartyOnly = Reader.GetBoolean(19); Server.ELCEnforced = Reader.GetBoolean(20); Server.ILREnforced = Reader.GetBoolean(21); Server.PWCUrl = Reader.GetString(22); Server.ServerDescription = Reader.GetString(23); lock (ActiveServerTable) { ActiveServerTable.Add(ServerAddress, Server); } ServersAdded += 1; } } Logger.Log(LogLevel.Normal, "NWServerTracker.InitializeDatabase(): Read {0} active servers from database.", ServersAdded); } catch (Exception e) { Logger.Log(LogLevel.Error, "NWServerTracker.InitializeDatabase(): Exception: {0}", e); MasterServer.Stop(); } }
/// <summary> /// Request a heartbeat on behalf of a server object. /// </summary> /// <param name="Server">Supplies the server object instance.</param> /// <returns>True if future heartbeats are enabled.</returns> public bool RequestHeartbeat(NWGameServer Server) { // // Check if heartbeats are enabled. If so, take the heartbeat lock // and request the heartbeat. // lock (HeartbeatLock) { if (!HeartbeatsEnabled) return false; MasterServer.RefreshServerStatus(Server.ServerAddress); } return true; }
/// <summary> /// Attempt to look up a game server by address, creating the server if /// requested (if it did not exist). /// </summary> /// <param name="ServerAddress">Supplies the server address to look up /// in the active server table.</param> /// <param name="Create">Supplies true if the server instance should be /// created if it did not exist.</param> /// <returns>The active server instance, else null if no server /// satisfied the given criteria.</returns> public NWGameServer LookupServerByAddress(IPEndPoint ServerAddress, bool Create = true) { NWGameServer Server; lock (ActiveServerTable) { if (!ActiveServerTable.TryGetValue(ServerAddress, out Server)) Server = null; if ((Create != false) && (Server == null)) { Server = new NWGameServer(MasterServer, ServerAddress); Server.Load(); ActiveServerTable.Add(ServerAddress, Server); } } return Server; }