/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { string FormattedMessage = String.Format( "</c><c=#FFFF00>{0}</c>", Message); NWScript.Vector3 v; v.x = v.y = v.z = 0.0f; Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); Script.FloatingTextStringOnCreature(FormattedMessage, PlayerObject, CLRScriptBase.FALSE, 5.0f, CLRScriptBase.COLOR_WHITE, CLRScriptBase.COLOR_WHITE, 0.0f, v); } Database.ACR_IncrementStatistic("BROADCAST_MESSAGE"); Script.WriteTimestampedLogEntry("Received broadcast notification: " + Message); }
/// <summary> /// Initialize the socket subsystem. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <returns>True if socket I/O is connected.</returns> public static bool Initialize(ACR_ServerCommunicator Script) { if (Initialized) return true; try { RecvfromDelegate = new RecvfromCallout(OnLowLevelReceive); RecvfromDelegateHandle = GCHandle.Alloc(RecvfromDelegate); try { SetRecvfromCallout(Marshal.GetFunctionPointerForDelegate(RecvfromDelegate)); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("SocketIo.Initialize: Exception: {0}", e)); RecvfromDelegate = null; RecvfromDelegateHandle.Free(); throw; } } catch { return false; } Initialized = true; return true; }
/// <summary> /// This method is called when the ClientEnter event fires for a /// player. Its purpose is to check whether the player has a pending /// resync request, and, if so, to execute the resync operation. /// </summary> /// <param name="State">Supplies the player object of the incoming /// player.</param> /// <param name="Script">Supplies the script object.</param> public static void OnClientEnter(uint PCObject, ACR_ServerCommunicator Script) { PlayerState State = Script.TryGetPlayerState(PCObject); if (State == null) { return; } var ResyncInfo = (from RS in PlayerResyncStates where RS.PlayerId == State.PlayerId select RS).FirstOrDefault(); if (ResyncInfo == null) { return; } // // Dequeue the resync state and start attempting to apply it to the // player object once the player has loaded. // PlayerResyncStates.Remove(ResyncInfo); ResynchronizePlayerState(ResyncInfo, PCObject, Script); }
/// <summary> /// Initialize the socket subsystem. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <returns>True if socket I/O is connected.</returns> public static bool Initialize(ACR_ServerCommunicator Script) { if (Initialized) { return(true); } try { RecvfromDelegate = new RecvfromCallout(OnLowLevelReceive); RecvfromDelegateHandle = GCHandle.Alloc(RecvfromDelegate); try { SetRecvfromCallout(Marshal.GetFunctionPointerForDelegate(RecvfromDelegate)); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("SocketIo.Initialize: Exception: {0}", e)); RecvfromDelegate = null; RecvfromDelegateHandle.Free(); throw; } } catch { return(false); } Initialized = true; return(true); }
/// <summary> /// This routine handles ALFA datagram protocol messages that have been /// received over the network. /// </summary> /// <param name="buf">Supplies the received data payload.</param> /// <param name="len">Supplies the length of received data.</param> /// <param name="Sender">Supplies the sender's address.</param> private static void OnDatagramReceive(IntPtr buf, int len, sockaddr_in Sender) { IPAddress Address = new IPAddress(Sender.sin_addr); int Port = (int)IPAddress.NetworkToHostOrder((short)Sender.sin_port); ServerNetworkManager NetworkManager = ACR_ServerCommunicator.GetNetworkManager(); byte[] Data = new byte[len]; Marshal.Copy(buf, Data, 0, len); NetworkManager.OnDatagramReceive(Data, Address, Port); }
/// <summary> /// Run the event queue down. All events in the queue are given a /// chance to run. /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void RunQueue(ACR_ServerCommunicator Script, ALFA.Database Database) { #if DEBUG_MODE Script.WriteTimestampedLogEntry(String.Format("ACR_ServerCommunicator: Running queue of {0} entries", EventQueue.Count)); #endif while (!Empty()) { IGameEventQueueEvent Event = EventQueue.Dequeue(); Event.DispatchEvent(Script, Database); } }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { string FormattedMessage = String.Format( "ACR_ServerCommunicator: Received unsupported IPC event {0}: {1}.{2}.{3}.{4}. Record ID was {5}.", EventType, P0, P1, P2, P3, RecordId); Script.WriteTimestampedLogEntry(FormattedMessage); }
/// <summary> /// This method is called to request an IPC channel latency measurement /// to a target server. /// </summary> /// <param name="PCObjectId">Supplies the PC object id of the /// requesting player.</param> /// <param name="ServerId">Supplies the server id to send the ping /// request to.</param> /// <param name="Script">Supplies the script object.</param> public static void SendPingToServer(uint PCObjectId, int ServerId, ACR_ServerCommunicator Script) { // // Package and serialize the ping state for transmission to the // remote server. The remote server will echo the ping back, and // HandleServerPingResponse will then be invoked via a reply // run script request for acr_ping_server_response. // PingState State = new PingState(PCObjectId, Environment.TickCount); Script.RunScriptOnServer(ServerId, "acr_server_ping_request", State.ToString()); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { // // If the event was for the local server, then don't re-broadcast // it. // if (Database.ACR_GetServerID() == Server.ServerId) { return; } string Message = String.Format( "<c=#FFFF00>Server {0} is now online.</c>", Server.Name); string ChatMessage = "</c>" + Message; foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) { continue; } if (!Script.IsCrossServerNotificationEnabled(PlayerObject)) { continue; } if ((Player.Flags & PlayerStateFlags.SendCrossServerNotificationsToCombatLog) != 0) { Script.SendMessageToPC(PlayerObject, Message); } else { Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, ChatMessage, CLRScriptBase.FALSE); } } #if DEBUG_MODE Script.WriteTimestampedLogEntry(Message); #endif }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { int SourceServerId = (SourceServer != null) ? SourceServer.ServerId : 0; Script.WriteTimestampedLogEntry(String.Format("RunScriptEvent.DispatchEvent: Executing script {0} ({1}) on request from {2}.", ScriptName, ScriptArgument, SourceServerId)); Script.AddScriptParameterInt(SourceServerId); Script.AddScriptParameterString(ScriptArgument); Script.ExecuteScriptEnhanced(ScriptName, Script.GetModule(), CLRScriptBase.TRUE); Database.ACR_IncrementStatistic("RUN_REMOTE_SCRIPT"); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Player.PlayerId) continue; Database.ACR_IncrementStatistic("DISCONNECT_PLAYER"); Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: Disconnecting player " + Script.GetPCPlayerName(PlayerObject) + " due to IPC request."); Script.BootPC(PlayerObject); return; } Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: No player '" + Player.PlayerName + "' found locally connected to disconnect."); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { int SourceServerId = (SourceServer != null) ? SourceServer.ServerId : 0; Script.WriteTimestampedLogEntry(String.Format("RunScriptEvent.DispatchEvent: Executing script {0} ({1}) on request from {2}.", ScriptName, ScriptArgument, SourceServerId)); Script.ClearScriptParams(); Script.AddScriptParameterInt(SourceServerId); Script.AddScriptParameterString(ScriptArgument); Script.ExecuteScriptEnhanced(ScriptName, Script.GetModule(), CLRScriptBase.TRUE); Database.ACR_IncrementStatistic("RUN_REMOTE_SCRIPT"); }
/// <summary> /// Initialize the server vault connector subsystem. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="ConnectionString">Supplies the connection string for /// the server vault.</param> /// <param name="VerboseLogging">Supplies true if verbose logging is /// enabled.</param> /// <returns>True if the server vault connector is /// initialized.</returns> public static bool Initialize(ACR_ServerCommunicator Script, string ConnectionString, bool VerboseLogging) { if (Initialized) { return(true); } try { StoreConnectionString = ConnectionString; FileStoreProvider.DefaultVaultConnectionString = ConnectionString; SynchronizeAccountFromVaultDelegate = new SynchronizeAccountFromVault(OnSynchronizeAccountFromVault); SynchronizeAccountFromVaultHandle = GCHandle.Alloc(SynchronizeAccountFromVaultDelegate); SynchronizeAccountFileToVaultDelegate = new SynchronizeAccountFileToVault(OnSynchronizeAccountFileToVault); SynchronizeAccountFileToVaultHandle = GCHandle.Alloc(SynchronizeAccountFileToVaultDelegate); try { if (SetStoragePluginCallbacks(Marshal.GetFunctionPointerForDelegate(SynchronizeAccountFromVaultDelegate), Marshal.GetFunctionPointerForDelegate(SynchronizeAccountFileToVaultDelegate), IntPtr.Zero) == 0) { throw new ApplicationException("Failed to install storage plugin callbacks."); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ServerVaultConnector.Initialize: Exception: {0}", e)); SynchronizeAccountFileToVaultDelegate = null; SynchronizeAccountFileToVaultHandle.Free(); SynchronizeAccountFromVaultDelegate = null; SynchronizeAccountFromVaultHandle.Free(); throw; } } catch { return(false); } Initialized = true; return(true); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Player.PlayerId) { continue; } Database.ACR_IncrementStatistic("DISCONNECT_PLAYER"); Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: Disconnecting player " + Script.GetPCPlayerName(PlayerObject) + " due to IPC request."); Script.BootPC(PlayerObject); return; } Script.WriteTimestampedLogEntry("DisconnectPlayerEvent.DispatchEvent: No player '" + Player.PlayerName + "' found locally connected to disconnect."); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { // // If the event was for the local server, then don't re-broadcast // it. // if (Database.ACR_GetServerID() == Server.ServerId) return; string Message = String.Format( "<c=#FFFF00>Server {0} is now offline.</c>", Server.Name); string ChatMessage = "</c>" + Message; foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) continue; if (!Script.IsCrossServerNotificationEnabled(PlayerObject)) continue; if ((Player.Flags & PlayerStateFlags.SendCrossServerNotificationsToCombatLog) != 0) { Script.SendMessageToPC(PlayerObject, Message); } else { Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, ChatMessage, CLRScriptBase.FALSE); } } #if DEBUG_MODE Script.WriteTimestampedLogEntry(Message); #endif }
/// <summary> /// Initialize the server vault connector subsystem. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="ConnectionString">Supplies the connection string for /// the server vault.</param> /// <param name="VerboseLogging">Supplies true if verbose logging is /// enabled.</param> /// <returns>True if the server vault connector is /// initialized.</returns> public static bool Initialize(ACR_ServerCommunicator Script, string ConnectionString, bool VerboseLogging) { if (Initialized) return true; try { StoreConnectionString = ConnectionString; FileStoreProvider.DefaultVaultConnectionString = ConnectionString; SynchronizeAccountFromVaultDelegate = new SynchronizeAccountFromVault(OnSynchronizeAccountFromVault); SynchronizeAccountFromVaultHandle = GCHandle.Alloc(SynchronizeAccountFromVaultDelegate); SynchronizeAccountFileToVaultDelegate = new SynchronizeAccountFileToVault(OnSynchronizeAccountFileToVault); SynchronizeAccountFileToVaultHandle = GCHandle.Alloc(SynchronizeAccountFileToVaultDelegate); try { if (SetStoragePluginCallbacks(Marshal.GetFunctionPointerForDelegate(SynchronizeAccountFromVaultDelegate), Marshal.GetFunctionPointerForDelegate(SynchronizeAccountFileToVaultDelegate), IntPtr.Zero) == 0) { throw new ApplicationException("Failed to install storage plugin callbacks."); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ServerVaultConnector.Initialize: Exception: {0}", e)); SynchronizeAccountFileToVaultDelegate = null; SynchronizeAccountFileToVaultHandle.Free(); SynchronizeAccountFromVaultDelegate = null; SynchronizeAccountFromVaultHandle.Free(); throw; } } catch { return false; } Initialized = true; return true; }
/// <summary> /// This when a cross-server ping response is received. Its purpose is /// to compute the channel latency and send a message to that effect to /// the requesting player. /// </summary> /// <param name="SourceServerId">Supplies the server id of the server /// that completed the ping request.</param> /// <param name="Arguments">Supplies the serialized state arguments /// string.</param> /// <param name="Script">Supplies the current script object.</param> /// <returns>TRUE on success, else FALSE on failure.</returns> public static int HandleServerPingResponse(int SourceServerId, string Arguments, ACR_ServerCommunicator Script) { int Tick = Environment.TickCount; try { PingState RemoteState; // // Deserialize the remote ping state. If null, a protocol // violation has occurred. // if ((RemoteState = PingState.FromString(Arguments)) == null) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.ServerLatencyMeasurer.HandleServerPingResponse({0}, {1}): Invalid request.", SourceServerId, Arguments)); return ACR_ServerCommunicator.FALSE; } string ServerName = Script.GetServerName(SourceServerId); if (ServerName == null) return ACR_ServerCommunicator.FALSE; Script.SendMessageToPC(RemoteState.PCObjectId, String.Format( "IPC channel latency to {0}: {1}ms", ServerName, Tick - RemoteState.TickCount)); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.ServerLatencyMeasurer.HandleServerPingResponse({0}, {1}): Exception: {0}", SourceServerId, Arguments, e)); return ACR_ServerCommunicator.FALSE; } return ACR_ServerCommunicator.TRUE; }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { string FormattedMessage = String.Format( "</c><c=#FFFF00>Server shutting down: {0}</c>", Message); NWScript.Vector3 v; v.x = v.y = v.z = 0.0f; Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); Script.FloatingTextStringOnCreature(FormattedMessage, PlayerObject, CLRScriptBase.FALSE, 5.0f, CLRScriptBase.COLOR_WHITE, CLRScriptBase.COLOR_WHITE, 0.0f, v); } Script.WriteTimestampedLogEntry("Received shutdown request: " + Message); Database.ACR_IncrementStatistic("SERVER_SHUTDOWN"); Script.SendInfrastructureIrcMessage(String.Format( "Server '{0}' shutting down or restarting: {1}", Script.GetName(Script.GetModule()), Message)); Script.DelayCommand(5.0f, delegate() { Database.ACR_FlushAllQueryQueues(); SystemInfo.ShutdownGameServer(Script); }); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { string VaultPath = SystemInfo.GetServerVaultPathForAccount(Player.Name); if (VaultPath == null) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Could not resolve vault path for player '{0}'.", Player.PlayerName)); return; } VaultPath += CharacterFileName; if (!File.Exists(VaultPath)) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Character file '{0}' for player '{1}' was not locally cached.", CharacterFileName, Player.PlayerName)); return; } try { File.Delete(VaultPath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Exception '{0}' removing cached character '{1}' for player '{2}'.", e, CharacterFileName, Player.PlayerName)); return; } Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Removed cached character '{0}' from player '{1}' vault cache.", CharacterFileName, Player.PlayerName)); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { PlayerState State = Script.TryGetPlayerState(PlayerObject); string RequestURL; // // If the player is logged off, then there's nothing to do. // if (State == null || State.IsDM) return; if (String.IsNullOrEmpty(AccountAssociationSecret)) return; RequestURL = AccountAssociator.GenerateAssociationURL(Script.GetPCPlayerName(PlayerObject), AccountAssociationSecret, AccountAssociationURL); Script.DisplayGuiScreen(PlayerObject, "acr_account_association", CLRScriptBase.FALSE, "acr_account_association.XML", CLRScriptBase.FALSE); Script.SetLocalGUIVariable(PlayerObject, "acr_account_association", 0, RequestURL); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Recipient.PlayerId) continue; string FormattedMessage = String.Format( "</c><c=#FFCC99>{0}: </c><c=#30DDCC>[Page] {1}</c>", Sender.PlayerName, Message); Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); break; } }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Recipient.PlayerId) { continue; } string FormattedMessage = String.Format( "</c><c=#FFCC99>{0}: </c><c=#30DDCC>[ServerTell] {1}</c>", Sender.CharacterName, Message); Script.SetLastTellFromPlayerId(PlayerObject, Sender.Player.PlayerId); Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); // // If this is the first time that we have delivered a // cross-server tell to this player (since login), remind them // of how to respond. // if ((Database.ACR_GetPCLocalFlags(PlayerObject) & ALFA.Database.ACR_PC_LOCAL_FLAG_SERVER_TELL_HELP) == 0) { Script.SendMessageToPC( PlayerObject, "To respond to a [ServerTell] from a player on a different server, type: #re [message], or #t \"character name\" [message], or #tp \"player name\" [message]. Quotes are optional unless the name has spaces. The #rt [message] command will send a tell to the last player that you had sent a tell to."); Database.ACR_SetPCLocalFlags( PlayerObject, Database.ACR_GetPCLocalFlags(PlayerObject) | ALFA.Database.ACR_PC_LOCAL_FLAG_SERVER_TELL_HELP); } break; } }
/// <summary> /// Construct a new PlayerState object. /// </summary> /// <param name="ObjectId">Supplies the NWScript object id of the PC /// object.</param> /// <param name="Communicator">Supplies the server communicator /// instance that the PlayerState object is bound to.</param> public PlayerState(uint ObjectId, ACR_ServerCommunicator Communicator) { ALFA.Database Database = Communicator.GetDatabase(); this.Communicator = Communicator; this.PCObjectId = ObjectId; this.PCCharacterId = Database.ACR_GetCharacterID(ObjectId); this.PCPlayerId = Database.ACR_GetPlayerID(ObjectId); this.StateFlags = (PlayerStateFlags)Database.ACR_GetPersistentInt(ObjectId, "ACR_COMMUNICATOR_STATE_FLAGS"); this.LatencyTickCount = 0; this.LatencyToServer = 0; this.ChatSelectLocalPlayersShown = 0; this.ChatSelectLocalDMsShown = 0; this.ChatSelectRemotePlayersShown = 0; this.ChatSelectRemoteDMsShown = 0; // // Upgrade any legacy database settings to their new format. // UpgradeLegacySettings(); }
/// <summary> /// Construct a new PlayerState object. /// </summary> /// <param name="ObjectId">Supplies the NWScript object id of the PC /// object.</param> /// <param name="Communicator">Supplies the server communicator /// instance that the PlayerState object is bound to.</param> public PlayerState(uint ObjectId, ACR_ServerCommunicator Communicator) { ALFA.Database Database = Communicator.GetDatabase(); this.Communicator = Communicator; this.PCObjectId = ObjectId; this.PCCharacterId = Database.ACR_GetCharacterID(ObjectId); this.PCPlayerId = Database.ACR_GetPlayerID(ObjectId); this.StateFlags = (PlayerStateFlags) Database.ACR_GetPersistentInt(ObjectId, "ACR_COMMUNICATOR_STATE_FLAGS"); this.LatencyTickCount = 0; this.LatencyToServer = 0; this.ChatSelectLocalPlayersShown = 0; this.ChatSelectLocalDMsShown = 0; this.ChatSelectRemotePlayersShown = 0; this.ChatSelectRemoteDMsShown = 0; // // Upgrade any legacy database settings to their new format. // UpgradeLegacySettings(); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Recipient.PlayerId) continue; string FormattedMessage = String.Format( "</c><c=#FFCC99>{0}: </c><c=#30DDCC>[ServerTell] {1}</c>", Sender.CharacterName, Message); Script.SetLastTellFromPlayerId(PlayerObject, Sender.Player.PlayerId); Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); // // If this is the first time that we have delivered a // cross-server tell to this player (since login), remind them // of how to respond. // if ((Database.ACR_GetPCLocalFlags(PlayerObject) & ALFA.Database.ACR_PC_LOCAL_FLAG_SERVER_TELL_HELP) == 0) { Script.SendMessageToPC( PlayerObject, "To respond to a [ServerTell] from a player on a different server, type: #re [message], or #t \"character name\" [message], or #tp \"player name\" [message]. Quotes are optional unless the name has spaces. The #rt [message] command will send a tell to the last player that you had sent a tell to."); Database.ACR_SetPCLocalFlags( PlayerObject, Database.ACR_GetPCLocalFlags(PlayerObject) | ALFA.Database.ACR_PC_LOCAL_FLAG_SERVER_TELL_HELP); } break; } }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { foreach (uint PlayerObject in Script.GetPlayers(true)) { if (Database.ACR_GetPlayerID(PlayerObject) != Recipient.PlayerId) { continue; } string FormattedMessage = String.Format( "</c><c=#FFCC99>{0}: </c><c=#30DDCC>[Page] {1}</c>", Sender.PlayerName, Message); Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, FormattedMessage, CLRScriptBase.FALSE); break; } }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { PlayerState State = Script.TryGetPlayerState(PlayerObject); string RequestURL; // // If the player is logged off, then there's nothing to do. // if (State == null || State.IsDM) { return; } if (String.IsNullOrEmpty(AccountAssociationSecret)) { return; } RequestURL = AccountAssociator.GenerateAssociationURL(Script.GetPCPlayerName(PlayerObject), AccountAssociationSecret, AccountAssociationURL); Script.DisplayGuiScreen(PlayerObject, "acr_account_association", CLRScriptBase.FALSE, "acr_account_association.XML", CLRScriptBase.FALSE); Script.SetLocalGUIVariable(PlayerObject, "acr_account_association", 0, RequestURL); }
/// <summary> /// Initialize the ServerNetworkManager instance. /// </summary> /// <param name="WorldManager">Supplies the game world manager to which /// the network manager instance is bound.</param> /// <param name="LocalServerId">Supplies the server id of the local /// server.</param> /// <param name="Script">Supplies the main script object.</param> public ServerNetworkManager(GameWorldManager WorldManager, int LocalServerId, ACR_ServerCommunicator Script) { SocketIo.Initialize(Script); this.WorldManager = WorldManager; this.LocalServerId = LocalServerId; }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { Script.SendMessageToPC(PlayerObject, Message); }
/// <summary> /// Process and apply content patches. A server restart is scheduled /// if required for the content patch. /// </summary> /// <param name="ContentPatchPath">Supplies the configured content /// patch base path, from the database config table.</param> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script) { bool ContentChanged = false; string Version = Database.ACR_GetHAKVersion(); List<ContentPatchFile> PatchFiles = new List<ContentPatchFile>(); string LocalContentPatchPath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches"; string LocalContentPatchHakPath = ALFA.SystemInfo.GetHakDirectory(); string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version); Database.ACR_SQLQuery(String.Format( "SELECT `FileName`, `Location`, `Checksum` FROM `content_patch_files` WHERE `HakVersion` = '{0}'", Database.ACR_SQLEncodeSpecialChars(Version))); while (Database.ACR_SQLFetch()) { ContentPatchFile PatchFile = new ContentPatchFile(); PatchFile.FileName = Database.ACR_SQLGetData(0); PatchFile.Location = Database.ACR_SQLGetData(1); PatchFile.Checksum = Database.ACR_SQLGetData(2); if (PatchFile.Location != "override" && PatchFile.Location != "hak") { continue; } if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName)) continue; PatchFiles.Add(PatchFile); } if (!Directory.Exists(LocalContentPatchPath)) Directory.CreateDirectory(LocalContentPatchPath); // // Remove entries in the ACR patch override directory that are not // listed in the patch table. These may be patches for a previous // version, and are not applicable. // foreach (string DirFile in Directory.GetFiles(LocalContentPatchPath)) { ContentPatchFile FoundPatch = (from PF in PatchFiles where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override") select PF).FirstOrDefault(); if (FoundPatch == null) { Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalContentPatchPath)); File.Delete(DirFile); ContentChanged = true; } } // // Verify that the MD5 checksum of each of the content files in the // ACR patch override directory matches the database's expected // checksum. If not (or if the file in question didn't exist), // then copy the new version over from the central vault. // using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider()) { foreach (ContentPatchFile PatchFile in PatchFiles) { bool Matched = false; string LocalPath = PatchFile.GetLocalPath(LocalContentPatchPath, LocalContentPatchHakPath); string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName); string LocalHashString = "<no such file>"; if (File.Exists(LocalPath) && File.Exists(RemotePath)) { LocalHashString = GetFileChecksum(LocalPath, MD5Csp); Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower()); } if (Matched) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).", PatchFile.FileName, LocalHashString)); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...", PatchFile.FileName, LocalHashString, PatchFile.Checksum)); // // The file needs to be updated. Copy it over and // re-validate the checksum. If the checksum did not // match, log an error and delete the file. // try { // // If we are patching a hak, rename it away so that // the file can be written to. // if (PatchFile.Location == "hak" && File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; if (File.Exists(OldFileName)) File.Delete(OldFileName); File.Move(LocalPath, OldFileName); Database.ACR_IncrementStatistic("CONTENT_PATCH_HAK"); } else if (PatchFile.Location == "override") { Database.ACR_IncrementStatistic("CONTENT_PATCH_OVERRIDE"); } try { File.Copy(RemotePath, LocalPath, true); } catch { if (PatchFile.Location == "hak" && File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } throw; } if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower()) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.", PatchFile.FileName, PatchFile.Checksum)); if (PatchFile.Location == "hak") { string OldFileName = LocalPath + ".old"; File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM"); } else { ContentChanged = true; Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.", PatchFile.FileName)); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.", e, PatchFile.FileName)); } } } } if (ContentChanged) Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT"); return ContentChanged; }
/// <summary> /// This method is called to resynchronize the GUI state of a player /// that executed a server to server portal, if the player is in an /// area. /// </summary> /// <param name="ResyncInfo">Supplies the resync block for the /// player. This contains the deserialized resynchronization command /// data.</param> /// <param name="PCObject">Supplies the player object id.</param> /// <param name="Script">Supplies the script object.</param> /// <param name="Tries">Supplies the count of retries.</param> private static void ResynchronizePlayerState(ResyncState ResyncInfo, uint PCObject, ACR_ServerCommunicator Script, int Tries = 0) { if (Script.GetIsObjectValid(PCObject) == ACR_ServerCommunicator.FALSE) { // // The player logged out while a resync request was pending. // Throw away the state as it is no longer needed on a full log // out and log in sequence. // return; } if ((Script.GetArea(PCObject) == ACR_ServerCommunicator.OBJECT_INVALID) || (Script.GetScriptHidden(PCObject) != ACR_ServerCommunicator.FALSE)) { // // The player may still be in transition or is not yet loaded. // Queue the request. // if (Tries < MAX_RESYNC_RETRIES) { Script.DelayCommand(RESYNC_RETRY_INTERVAL, delegate() { ResynchronizePlayerState(ResyncInfo, PCObject, Script, Tries + 1); }); } return; } PlayerState State = Script.TryGetPlayerState(PCObject); if (State == null) return; // // Area transition has finished. Apply the GUI state now. // State.ChatSelectGUIExpanded = ((ResyncInfo.ResyncFlags & RESYNC_FLAG_CHAT_SELECT_EXPANDED) != 0); State.UpdateChatSelectGUIHeaders(); Script.SendMessageToPC(PCObject, "Server to server portal completed."); Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.ResynchronizePlayerState: Resynchronized player GUI state for player {0} after server-to-server portal.", Script.GetName(PCObject))); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { // // If the event was for a player logging on to the local server, // then don't re-broadcast it. // foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) { continue; } if (!Player.CharacterIdsShown.Contains(Character.CharacterId)) { string sPlayerListBox = ""; if (Server.ServerId == Script.GetDatabase().ACR_GetServerID() || Script.GetLocalInt(PlayerObject, "chatselect_expanded") == 0) { if (IsDM == true) { sPlayerListBox = "LocalDMList"; Player.ChatSelectLocalDMsShown += 1; } else { sPlayerListBox = "LocalPlayerList"; Player.ChatSelectLocalPlayersShown += 1; } if (Server.ServerId == Script.GetDatabase().ACR_GetServerID()) { Script.AddListBoxRow(Player.ObjectId, "ChatSelect", sPlayerListBox, Character.CharacterName, "RosterData=/t \"" + Character.CharacterName + "\"", "", "5=/t \"" + Character.CharacterName + "\" ", ""); } else { if (Player.Flags.HasFlag(PlayerStateFlags.ChatSelectShowLocalPlayersOnlyWhenCollapsed)) { continue; } Script.AddListBoxRow(Player.ObjectId, "ChatSelect", sPlayerListBox, Character.CharacterName, "RosterData=#t \"" + Character.CharacterName + "\"", "", "5=#t \"" + Character.CharacterName + "\" ", ""); } } else { if (IsDM == true) { sPlayerListBox = "RemoteDMList"; Player.ChatSelectRemoteDMsShown += 1; } else { sPlayerListBox = "RemotePlayerList"; Player.ChatSelectRemotePlayersShown += 1; } Script.AddListBoxRow(Player.ObjectId, "ChatSelect", sPlayerListBox, Character.CharacterName, "RosterData=#t \"" + Character.CharacterName + "\"", "", "5=#t \"" + Character.CharacterName + "\" ", ""); } Player.CharacterIdsShown.Add(Character.CharacterId); Player.UpdateChatSelectGUIHeaders(); } } if (Database.ACR_GetServerID() == Server.ServerId) { return; } string Message = String.Format( "{0}<c=#FFA500>{1} ({2}) joined {3}.</c>", // <c=Orange...> IsDM ? "<c=#99CCFF>[DM] </c>": "", Character.Name, Character.Player.Name, Server.Name); string ChatMessage = "</c>" + Message; foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) { continue; } if (!Script.IsCrossServerNotificationEnabled(PlayerObject)) { continue; } if ((Player.Flags & PlayerStateFlags.SendCrossServerNotificationsToCombatLog) != 0) { Script.SendMessageToPC(PlayerObject, Message); } else { Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, ChatMessage, CLRScriptBase.FALSE); } } #if DEBUG_MODE Script.WriteTimestampedLogEntry(Message); #endif }
/// <summary> /// Process and apply content patches. A server restart is scheduled /// if required for the content patch. /// </summary> /// <param name="ContentPatchPath">Supplies the configured content /// patch base path, from the database config table.</param> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <param name="ConnectionString">Supplies the updater file store /// connection string.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script, string ConnectionString) { bool ContentChanged = false; string Version = Database.ACR_GetHAKVersion(); List<ContentPatchFile> PatchFiles = new List<ContentPatchFile>(); ContentPatchPaths LocalPaths = new ContentPatchPaths() { HakPath = ALFA.SystemInfo.GetHakDirectory(), OverridePath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches" }; string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version); string FileStoreContentPatchPath = String.Format("{0}/{1}", ContentPatchPath, Version).Replace('\\', '/'); bool RecompileModule = false; bool SentNotification = false; Database.ACR_SQLQuery(String.Format( "SELECT `FileName`, `Location`, `Checksum`, `RecompileModule` FROM `content_patch_files` WHERE `HakVersion` = '{0}'", Database.ACR_SQLEncodeSpecialChars(Version))); while (Database.ACR_SQLFetch()) { ContentPatchFile PatchFile = new ContentPatchFile(); PatchFile.FileName = Database.ACR_SQLGetData(0); PatchFile.Location = Database.ACR_SQLGetData(1); PatchFile.Checksum = Database.ACR_SQLGetData(2); PatchFile.RecompileModule = ALFA.Database.ACR_ConvertDatabaseStringToBoolean(Database.ACR_SQLGetData(3)); if (PatchFile.Location != "override" && PatchFile.Location != "hak") { continue; } if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName)) continue; PatchFiles.Add(PatchFile); } if (!Directory.Exists(LocalPaths.OverridePath)) Directory.CreateDirectory(LocalPaths.OverridePath); // // Remove entries in the ACR patch override directory that are not // listed in the patch table. These may be patches for a previous // version, and are not applicable. // foreach (string DirFile in Directory.GetFiles(LocalPaths.OverridePath)) { ContentPatchFile FoundPatch = (from PF in PatchFiles where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override") select PF).FirstOrDefault(); if (FoundPatch == null) { Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalPaths.OverridePath)); File.Delete(DirFile); ContentChanged = true; } } // // Verify that the MD5 checksum of each of the content files in the // ACR patch override directory matches the database's expected // checksum. If not (or if the file in question didn't exist), // then copy the new version over from the central vault. // using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider()) { foreach (ContentPatchFile PatchFile in PatchFiles) { bool Matched = false; bool Rename = false; FileUpdateMethod UpdateMethod; string LocalPath = PatchFile.GetLocalPath(LocalPaths); string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName); string FileStorePath = String.Format("{0}/{1}", FileStoreContentPatchPath, PatchFile.FileName).Replace('\\', '/'); string LocalHashString = "<no such file>"; string TransferTempFilePath = LocalPath + ".patchxfer"; if (File.Exists(LocalPath)) { LocalHashString = GetFileChecksum(LocalPath, MD5Csp); Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower()); } if (Matched) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).", PatchFile.FileName, LocalHashString)); } else { if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2}.", e, TransferTempFilePath, PatchFile.FileName)); } } Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...", PatchFile.FileName, LocalHashString, PatchFile.Checksum)); if (PatchFile.RecompileModule) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} requires a module recompile, flagging module for recompilation.", PatchFile.FileName)); RecompileModule = true; } if (!SentNotification) { Script.SendInfrastructureIrcMessage(String.Format( "Server '{0}' is applying a content patch, and will restart shortly.", Script.GetName(Script.GetModule()))); SentNotification = true; } Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is updating content file '{1}'.", Script.GetName(Script.GetModule()), PatchFile.FileName)); // // The file needs to be updated. Copy it over and // re-validate the checksum. If the checksum did not // match, log an error and delete the file. // try { try { // // Try first to download via the file store // provider. If that fails (e.g. the file // store is not supported), then fall back to // the traditional remote vault share transfer // mechanism. // try { DownloadContentPatchFromFileStore(FileStorePath, TransferTempFilePath, ConnectionString, Script); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.ProcessContentPatches: Couldn't retrieve uncompressed file {0} from Azure, falling back to file share, due to exception: {1}", FileStorePath, e)); File.Copy(RemotePath, TransferTempFilePath, true); } } catch { throw; } UpdateMethod = PatchFile.UpdateMethod; // // If we are patching a hak, rename it away so that // the file can be written to. // switch (UpdateMethod) { case FileUpdateMethod.FileUpdateMethodRename: if (File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; if (File.Exists(OldFileName)) File.Delete(OldFileName); File.Move(LocalPath, OldFileName); Rename = true; } break; case FileUpdateMethod.FileUpdateMethodDirectReplace: break; } Database.ACR_IncrementStatistic("CONTENT_PATCH_" + PatchFile.Location.ToUpper()); try { if (Rename) File.Move(TransferTempFilePath, LocalPath); else File.Copy(TransferTempFilePath, LocalPath, true); } catch { if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) File.Delete(LocalPath); } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } throw; } if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower()) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.", PatchFile.FileName, PatchFile.Checksum)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had checksum mismatch for content patch file {1} after hotfix file deployment, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) File.Delete(LocalPath); } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM"); } else { ContentChanged = true; Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.", PatchFile.FileName)); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.", e, PatchFile.FileName)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception deploying content patch file {1}, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); } } if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2} after patching completed.", e, TransferTempFilePath, PatchFile.FileName)); } } } } // // Update autodownloader configuration, as necessary. // try { if (ProcessModuleDownloaderResourcesUpdates(Database, Script)) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: Autodownloader configuration updated."); ContentChanged = true; } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} updating autodownloader configuration.", e)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception updating autodownloader configuration.", Script.GetName(Script.GetModule()))); } if (ContentChanged) { if (RecompileModule) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: A module recompile is required; recompiling module..."); CompileModuleScripts(Script, Database); } Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' restarting after content patch deployment (old HAK ACR version {1} build date {2}, old module ACR version {3}).", Script.GetName(Script.GetModule()), Database.ACR_GetHAKVersion(), Database.ACR_GetHAKBuildDate(), Database.ACR_GetVersion())); } return ContentChanged; }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { // // If the event was for a player logging off of the local server, // then don't re-broadcast it. // foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) continue; if (Player.CharacterIdsShown.Contains(Character.CharacterId)) { string sPlayerListBox = ""; if (Server.ServerId == Script.GetDatabase().ACR_GetServerID() || Script.GetLocalInt(PlayerObject, "chatselect_expanded") == 0) { if (IsDM == true) { sPlayerListBox = "LocalDMList"; Player.ChatSelectLocalDMsShown -= 1; } else { sPlayerListBox = "LocalPlayerList"; Player.ChatSelectLocalPlayersShown -= 1; } } else { if (IsDM == true) { sPlayerListBox = "RemoteDMList"; Player.ChatSelectRemoteDMsShown -= 1; } else { sPlayerListBox = "RemotePlayerList"; Player.ChatSelectRemotePlayersShown -= 1; } } Script.RemoveListBoxRow(Player.ObjectId, "ChatSelect", sPlayerListBox, Character.CharacterName); Player.CharacterIdsShown.Remove(Character.CharacterId); Player.UpdateChatSelectGUIHeaders(); } } if (Database.ACR_GetServerID() == Server.ServerId) return; string Message = String.Format( "{0}<c=#FFDAB9>{1} ({2}) left {3}.</c>", // <c=Peachpuff...> IsDM ? "<c=#99CCFF>[DM] </c>" : "", Character.Name, Character.Player.Name, Server.Name); string ChatMessage = "</c>" + Message; foreach (uint PlayerObject in Script.GetPlayers(true)) { PlayerState Player = Script.TryGetPlayerState(PlayerObject); if (Player == null) continue; if (!Script.IsCrossServerNotificationEnabled(PlayerObject)) continue; if ((Player.Flags & PlayerStateFlags.SendCrossServerNotificationsToCombatLog) != 0) { Script.SendMessageToPC(PlayerObject, Message); } else { Script.SendChatMessage( CLRScriptBase.OBJECT_INVALID, PlayerObject, CLRScriptBase.CHAT_MODE_SERVER, ChatMessage, CLRScriptBase.FALSE); } } #if DEBUG_MODE Script.WriteTimestampedLogEntry(Message); #endif }
/// <summary> /// Process any updates to moduledownloaderresources.xml. /// </summary> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessModuleDownloaderResourcesUpdates(ALFA.Database Database, ACR_ServerCommunicator Script) { bool ContentChanged = false; // // Check the database for the expected download server configurations. // Database.ACR_SQLQuery("SELECT `Hash`, `DownloadHash`, `Name`, `DLSize`, `Size` FROM `content_download_config`"); List<DownloadConfiguration> hakConfigs = new List<DownloadConfiguration>(); // // Build a list of expected download configurations. // while (Database.ACR_SQLFetch()) { DownloadConfiguration downloadConfig = new DownloadConfiguration(); downloadConfig.Hash = Database.ACR_SQLGetData(0); downloadConfig.DownloadHash = Database.ACR_SQLGetData(1); downloadConfig.Name = Database.ACR_SQLGetData(2); downloadConfig.DLSize = Database.ACR_SQLGetData(3); downloadConfig.Size = Database.ACR_SQLGetData(4); if (String.IsNullOrEmpty(downloadConfig.Hash) || String.IsNullOrEmpty(downloadConfig.DownloadHash) || String.IsNullOrEmpty(downloadConfig.Name) || String.IsNullOrEmpty(downloadConfig.DLSize) || String.IsNullOrEmpty(downloadConfig.Size)) { continue; } hakConfigs.Add(downloadConfig); } // // If we have any configuration to do, we loop through and compare the // configuration on the database with the configuration on the server, // updating the downloadresources xml if it is new. // if (hakConfigs.Count > 0) { XmlDocument moduleDownloadResources = new XmlDocument(); moduleDownloadResources.Load(ALFA.SystemInfo.GetModuleDirectory() + "\\moduledownloaderresources.xml"); XmlElement downloadResources = moduleDownloadResources.DocumentElement; foreach (DownloadConfiguration config in hakConfigs) { foreach (XmlNode node in downloadResources.ChildNodes) { if (node.Attributes["name"].Value == config.Name) { if (node.Attributes["hash"].Value != config.Hash) { node.Attributes["hash"].Value = config.Hash; ContentChanged = true; } if (node.Attributes["downloadHash"].Value != config.DownloadHash) { node.Attributes["downloadHash"].Value = config.DownloadHash; ContentChanged = true; } if (node.Attributes["dlsize"].Value != config.DLSize) { node.Attributes["dlsize"].Value = config.DLSize; ContentChanged = true; } if (node.Attributes["size"].Value != config.Size) { node.Attributes["size"].Value = config.Size; ContentChanged = true; } if (ContentChanged) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessModuleDownloaderResourcesUpdates: Updated downloader resource {0} (hash {1}).", config.Name, config.Hash)); } } } } if (ContentChanged) moduleDownloadResources.Save(ALFA.SystemInfo.GetModuleDirectory() + "\\moduledownloaderresources.xml"); } return ContentChanged; }
/// <summary> /// Run the event queue down. All events in the queue are given a /// chance to run. /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void RunQueue(ACR_ServerCommunicator Script, ALFA.Database Database) { EventQueue.RunQueue(Script, Database); EventsQueued = false; }
/// <summary> /// This method is called to resynchronize the GUI state of a player /// that executed a server to server portal, if the player is in an /// area. /// </summary> /// <param name="ResyncInfo">Supplies the resync block for the /// player. This contains the deserialized resynchronization command /// data.</param> /// <param name="PCObject">Supplies the player object id.</param> /// <param name="Script">Supplies the script object.</param> /// <param name="Tries">Supplies the count of retries.</param> private static void ResynchronizePlayerState(ResyncState ResyncInfo, uint PCObject, ACR_ServerCommunicator Script, int Tries = 0) { if (Script.GetIsObjectValid(PCObject) == ACR_ServerCommunicator.FALSE) { // // The player logged out while a resync request was pending. // Throw away the state as it is no longer needed on a full log // out and log in sequence. // return; } if ((Script.GetArea(PCObject) == ACR_ServerCommunicator.OBJECT_INVALID) || (Script.GetScriptHidden(PCObject) != ACR_ServerCommunicator.FALSE)) { // // The player may still be in transition or is not yet loaded. // Queue the request. // if (Tries < MAX_RESYNC_RETRIES) { Script.DelayCommand(RESYNC_RETRY_INTERVAL, delegate() { ResynchronizePlayerState(ResyncInfo, PCObject, Script, Tries + 1); }); } return; } PlayerState State = Script.TryGetPlayerState(PCObject); if (State == null) { return; } // // Area transition has finished. Apply the GUI state now. // State.ChatSelectGUIExpanded = ((ResyncInfo.ResyncFlags & RESYNC_FLAG_CHAT_SELECT_EXPANDED) != 0); State.UpdateChatSelectGUIHeaders(); Script.SendMessageToPC(PCObject, "Server to server portal completed."); Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.ResynchronizePlayerState: Resynchronized player GUI state for player {0} after server-to-server portal.", Script.GetName(PCObject))); }
/// <summary> /// Recompile all scripts in the module. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="Database">Supplies the database connection.</param> private static void CompileModuleScripts(ACR_ServerCommunicator Script, ALFA.Database Database) { ALFA.ScriptCompiler.CompilerResult Result; string CompilerOptions = Script.GetLocalString(Script.GetModule(), "ACR_MOD_COMPILER_OPTIONS"); List<string> CompilerOutput = new List<string>(); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Compiling module scripts with compiler options '{0}'...", CompilerOptions)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is recompiling module scripts.", Script.GetName(Script.GetModule()))); Result = ALFA.ScriptCompiler.CompileScript("*.nss", CompilerOptions, delegate(string Line) { if (!String.IsNullOrWhiteSpace(Line)) { Script.WriteTimestampedLogEntry(Line); } return false; }); foreach (string Message in Result.Warnings) { try { CompilerOutput.Add(Message); } catch (Exception) { } } if (Result.Compiled) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.CompileModuleScripts: Module successfully recompiled."); Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' successfully recompiled module with {1} warning(s) for content patch deployment.", Script.GetName(Script.GetModule()), Result.Warnings.Count)); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: {0} error(s) compiling module!", Result.Errors.Count)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had {1} error(s), {2} warning(s) recompiling module for content patch deployment.", Script.GetName(Script.GetModule()), Result.Errors.Count, Result.Warnings.Count)); foreach (string Message in Result.Errors) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Error '{0}'.", Message)); try { CompilerOutput.Add(Message); } catch (Exception) { } } Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE_FAILED"); } // // Save compiler output to a temporary file for later retrieval. // try { string FileName = String.Format("{0}{1}ALFAModuleRecompile.log", Path.GetTempPath(), Path.DirectorySeparatorChar); File.WriteAllLines(FileName, CompilerOutput); } catch (Exception) { } }
/// <summary> /// Download a content patch file from a file store. Currently, Azure /// file stores are assumed. Both compressed and uncompressed versions /// of the file are tried in respective order. /// </summary> /// <param name="FileStorePath">Supplies the remote file name that /// designates the file to download.</param> /// <param name="LocalFileName">Supplies the local file name to /// download to.</param> /// <param name="ConnectionString">Supplies the file store connection /// string.</param> /// <param name="Script">Supplies the script object.</param> private static void DownloadContentPatchFromFileStore(string FileStorePath, string LocalFileName, string ConnectionString, ACR_ServerCommunicator Script) { if (String.IsNullOrEmpty(ConnectionString)) throw new NotSupportedException(); // // Initialize the file store provider. // FileStore UpdaterStore = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer UpdaterContainer = UpdaterStore.GetContainerReference(FileStoreNamespace.ACRUpdater); FileStoreFile UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath + ".gzip"); // // First attempt to retrieve a gzip compressed version of the file // to patch. If that fails then fall back to a plaintext version. // try { using (MemoryStream MemStream = new MemoryStream()) { UpdaterFile.Read(MemStream); MemStream.Position = 0; using (FileStream OutStream = File.Create(LocalFileName)) { using (GZipStream CompressedStream = new GZipStream(MemStream, CompressionMode.Decompress)) { CompressedStream.CopyTo(OutStream); } } } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.DownloadContentPatchFromFileStore: Couldn't retrieve compressed file {0} from Azure, trying uncompressed file, due to exception: {1}", FileStorePath, e)); UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath); using (FileStream OutStream = File.Create(LocalFileName)) { UpdaterFile.Read(OutStream); } } }
/// <summary> /// This method is called when the ClientEnter event fires for a /// player. Its purpose is to check whether the player has a pending /// resync request, and, if so, to execute the resync operation. /// </summary> /// <param name="State">Supplies the player object of the incoming /// player.</param> /// <param name="Script">Supplies the script object.</param> public static void OnClientEnter(uint PCObject, ACR_ServerCommunicator Script) { PlayerState State = Script.TryGetPlayerState(PCObject); if (State == null) return; var ResyncInfo = (from RS in PlayerResyncStates where RS.PlayerId == State.PlayerId select RS).FirstOrDefault(); if (ResyncInfo == null) return; // // Dequeue the resync state and start attempting to apply it to the // player object once the player has loaded. // PlayerResyncStates.Remove(ResyncInfo); ResynchronizePlayerState(ResyncInfo, PCObject, Script); }
/// <summary> /// This method is called when a cross-server chat select /// resynchronization request is received. Its purpose is to scan the /// player list to check whether the argument player is online, and to /// activate chat select GUI resynchronization if appropriate, else to /// queue the resynchronization until the player does log in. /// </summary> /// <param name="SourceServerId">Supplies the server id of the server /// that requested the GUI resynchronization.</param> /// <param name="ResyncCommand">Supplies the GUI resynchronizer command /// line as generated by SendGUIStateToServer().</param> /// <param name="Script">Supplies the current script object.</param> /// <returns>TRUE on success, else FALSE on failure.</returns> public static int HandleGUIResync(int SourceServerId, string ResyncCommand, ACR_ServerCommunicator Script) { try { ResyncState RemoteResyncInfo; // // Deserialize the remote resynchronization state. On null, an // obviously invalid request was received. Otherwise a protocol // violation that is atypical was received and an exception is // raised. // if ((RemoteResyncInfo = ResyncState.FromString(ResyncCommand)) == null) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.HandleGUIResync({0}, {1}): Invalid request.", SourceServerId, ResyncCommand)); return(ACR_ServerCommunicator.FALSE); } // // If a request was not already enqueued for this player, then // enqueue it. // var ResyncInfo = (from RS in PlayerResyncStates where RS.PlayerId == RemoteResyncInfo.PlayerId select RS).FirstOrDefault(); if (ResyncInfo == null) { // // If the player is logged on, directly enqueue the execute // request now. Otherwise, wait for the ClientEnter event // as the player might still reside on the remote server. // foreach (uint PCObject in Script.GetPlayers(true)) { PlayerState State; if ((State = Script.TryGetPlayerState(PCObject)) == null) { continue; } if (State.PlayerId != RemoteResyncInfo.PlayerId) { continue; } ResynchronizePlayerState(RemoteResyncInfo, PCObject, Script); return(ACR_ServerCommunicator.TRUE); } // // Enqueue the request so that it can be processed at // ClientEnter time. // PlayerResyncStates.Add(RemoteResyncInfo); } else { // // Update the resync flags to match the new request. // ResyncInfo.ResyncFlags = RemoteResyncInfo.ResyncFlags; } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.HandleGUIResync({0}, {1}): Exception: {0}", SourceServerId, ResyncCommand, e)); return(ACR_ServerCommunicator.FALSE); } return(ACR_ServerCommunicator.TRUE); }
/// <summary> /// This method is called when a portal transition has been committed /// to send a player to a remote server. /// </summary> /// <param name="State">Supplies the local state for the player that is /// committed to a portal transition. /// </param> /// <param name="Server">Supplies the destination server for the portal /// event.</param> /// <param name="Script">Supplies the script object.</param> public static void SendGUIStateToServer(PlayerState State, GameServer Server, ACR_ServerCommunicator Script) { // // Build the resynchronization command. The resynchronization // command is parsed by HandleGUIResync(). Note that the remote // and local servers may not be of the same version, i.e. the // remote server may not understand fields that are created by the // local server if it is of a newer version. // ResyncState ResyncInfo = new ResyncState(State.PlayerId, 0); if (State.ChatSelectGUIExpanded) ResyncInfo.ResyncFlags |= RESYNC_FLAG_CHAT_SELECT_EXPANDED; // // Dispatch the resync script execution request to the remote // server. The remote server will process it when the request has // been received. // Script.RunScriptOnServer(Server.ServerId, "acr_resync_gui", ResyncInfo.ToString()); }
/// <summary> /// Process any updates to moduledownloaderresources.xml. /// </summary> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessModuleDownloaderResourcesUpdates(ALFA.Database Database, ACR_ServerCommunicator Script) { bool ContentChanged = false; // // Check the database for the expected download server configurations. // Database.ACR_SQLQuery("SELECT `Hash`, `DownloadHash`, `Name`, `DLSize`, `Size` FROM `content_download_config`"); List <DownloadConfiguration> hakConfigs = new List <DownloadConfiguration>(); // // Build a list of expected download configurations. // while (Database.ACR_SQLFetch()) { DownloadConfiguration downloadConfig = new DownloadConfiguration(); downloadConfig.Hash = Database.ACR_SQLGetData(0); downloadConfig.DownloadHash = Database.ACR_SQLGetData(1); downloadConfig.Name = Database.ACR_SQLGetData(2); downloadConfig.DLSize = Database.ACR_SQLGetData(3); downloadConfig.Size = Database.ACR_SQLGetData(4); if (String.IsNullOrEmpty(downloadConfig.Hash) || String.IsNullOrEmpty(downloadConfig.DownloadHash) || String.IsNullOrEmpty(downloadConfig.Name) || String.IsNullOrEmpty(downloadConfig.DLSize) || String.IsNullOrEmpty(downloadConfig.Size)) { continue; } hakConfigs.Add(downloadConfig); } // // If we have any configuration to do, we loop through and compare the // configuration on the database with the configuration on the server, // updating the downloadresources xml if it is new. // if (hakConfigs.Count > 0) { XmlDocument moduleDownloadResources = new XmlDocument(); moduleDownloadResources.Load(ALFA.SystemInfo.GetModuleDirectory() + "\\moduledownloaderresources.xml"); XmlElement downloadResources = moduleDownloadResources.DocumentElement; foreach (DownloadConfiguration config in hakConfigs) { foreach (XmlNode node in downloadResources.ChildNodes) { if (node.Attributes["name"].Value == config.Name) { if (node.Attributes["hash"].Value != config.Hash) { node.Attributes["hash"].Value = config.Hash; ContentChanged = true; } if (node.Attributes["downloadHash"].Value != config.DownloadHash) { node.Attributes["downloadHash"].Value = config.DownloadHash; ContentChanged = true; } if (node.Attributes["dlsize"].Value != config.DLSize) { node.Attributes["dlsize"].Value = config.DLSize; ContentChanged = true; } if (node.Attributes["size"].Value != config.Size) { node.Attributes["size"].Value = config.Size; ContentChanged = true; } if (ContentChanged) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessModuleDownloaderResourcesUpdates: Updated downloader resource {0} (hash {1}).", config.Name, config.Hash)); } } } } if (ContentChanged) { moduleDownloadResources.Save(ALFA.SystemInfo.GetModuleDirectory() + "\\moduledownloaderresources.xml"); } } return(ContentChanged); }
/// <summary> /// This method is called when a cross-server chat select /// resynchronization request is received. Its purpose is to scan the /// player list to check whether the argument player is online, and to /// activate chat select GUI resynchronization if appropriate, else to /// queue the resynchronization until the player does log in. /// </summary> /// <param name="SourceServerId">Supplies the server id of the server /// that requested the GUI resynchronization.</param> /// <param name="ResyncCommand">Supplies the GUI resynchronizer command /// line as generated by SendGUIStateToServer().</param> /// <param name="Script">Supplies the current script object.</param> /// <returns>TRUE on success, else FALSE on failure.</returns> public static int HandleGUIResync(int SourceServerId, string ResyncCommand, ACR_ServerCommunicator Script) { try { ResyncState RemoteResyncInfo; // // Deserialize the remote resynchronization state. On null, an // obviously invalid request was received. Otherwise a protocol // violation that is atypical was received and an exception is // raised. // if ((RemoteResyncInfo = ResyncState.FromString(ResyncCommand)) == null) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.HandleGUIResync({0}, {1}): Invalid request.", SourceServerId, ResyncCommand)); return ACR_ServerCommunicator.FALSE; } // // If a request was not already enqueued for this player, then // enqueue it. // var ResyncInfo = (from RS in PlayerResyncStates where RS.PlayerId == RemoteResyncInfo.PlayerId select RS).FirstOrDefault(); if (ResyncInfo == null) { // // If the player is logged on, directly enqueue the execute // request now. Otherwise, wait for the ClientEnter event // as the player might still reside on the remote server. // foreach (uint PCObject in Script.GetPlayers(true)) { PlayerState State; if ((State = Script.TryGetPlayerState(PCObject)) == null) continue; if (State.PlayerId != RemoteResyncInfo.PlayerId) continue; ResynchronizePlayerState(RemoteResyncInfo, PCObject, Script); return ACR_ServerCommunicator.TRUE; } // // Enqueue the request so that it can be processed at // ClientEnter time. // PlayerResyncStates.Add(RemoteResyncInfo); } else { // // Update the resync flags to match the new request. // ResyncInfo.ResyncFlags = RemoteResyncInfo.ResyncFlags; } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.GUIResynchronizer.HandleGUIResync({0}, {1}): Exception: {0}", SourceServerId, ResyncCommand, e)); return ACR_ServerCommunicator.FALSE; } return ACR_ServerCommunicator.TRUE; }
/// <summary> /// Process and apply content patches. A server restart is scheduled /// if required for the content patch. /// </summary> /// <param name="ContentPatchPath">Supplies the configured content /// patch base path, from the database config table.</param> /// <param name="Database">Supplies the database object.</param> /// <param name="Script">Supplies the script object.</param> /// <param name="ConnectionString">Supplies the updater file store /// connection string.</param> /// <returns>True if a patch was applied and a reboot is required for /// it to take effect.</returns> public static bool ProcessContentPatches(string ContentPatchPath, ALFA.Database Database, ACR_ServerCommunicator Script, string ConnectionString) { bool ContentChanged = false; string Version = Database.ACR_GetHAKVersion(); List <ContentPatchFile> PatchFiles = new List <ContentPatchFile>(); ContentPatchPaths LocalPaths = new ContentPatchPaths() { HakPath = ALFA.SystemInfo.GetHakDirectory(), OverridePath = ALFA.SystemInfo.GetOverrideDirectory() + "ACR_ContentPatches" }; string RemoteContentPatchPath = String.Format("{0}{1}\\{2}", ALFA.SystemInfo.GetCentralVaultPath(), ContentPatchPath, Version); string FileStoreContentPatchPath = String.Format("{0}/{1}", ContentPatchPath, Version).Replace('\\', '/'); bool RecompileModule = false; bool SentNotification = false; Database.ACR_SQLQuery(String.Format( "SELECT `FileName`, `Location`, `Checksum`, `RecompileModule` FROM `content_patch_files` WHERE `HakVersion` = '{0}'", Database.ACR_SQLEncodeSpecialChars(Version))); while (Database.ACR_SQLFetch()) { ContentPatchFile PatchFile = new ContentPatchFile(); PatchFile.FileName = Database.ACR_SQLGetData(0); PatchFile.Location = Database.ACR_SQLGetData(1); PatchFile.Checksum = Database.ACR_SQLGetData(2); PatchFile.RecompileModule = ALFA.Database.ACR_ConvertDatabaseStringToBoolean(Database.ACR_SQLGetData(3)); if (PatchFile.Location != "override" && PatchFile.Location != "hak") { continue; } if (!ALFA.SystemInfo.IsSafeFileName(PatchFile.FileName)) { continue; } PatchFiles.Add(PatchFile); } if (!Directory.Exists(LocalPaths.OverridePath)) { Directory.CreateDirectory(LocalPaths.OverridePath); } // // Remove entries in the ACR patch override directory that are not // listed in the patch table. These may be patches for a previous // version, and are not applicable. // foreach (string DirFile in Directory.GetFiles(LocalPaths.OverridePath)) { ContentPatchFile FoundPatch = (from PF in PatchFiles where (PF.FileName.Equals(Path.GetFileName(DirFile), StringComparison.InvariantCultureIgnoreCase) && PF.Location == "override") select PF).FirstOrDefault(); if (FoundPatch == null) { Database.ACR_IncrementStatistic("CONTENT_PATCH_REMOVE_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Removing extraneous file {0} from {1}", Path.GetFileName(DirFile), LocalPaths.OverridePath)); File.Delete(DirFile); ContentChanged = true; } } // // Verify that the MD5 checksum of each of the content files in the // ACR patch override directory matches the database's expected // checksum. If not (or if the file in question didn't exist), // then copy the new version over from the central vault. // using (MD5CryptoServiceProvider MD5Csp = new MD5CryptoServiceProvider()) { foreach (ContentPatchFile PatchFile in PatchFiles) { bool Matched = false; bool Rename = false; FileUpdateMethod UpdateMethod; string LocalPath = PatchFile.GetLocalPath(LocalPaths); string RemotePath = String.Format("{0}\\{1}", RemoteContentPatchPath, PatchFile.FileName); string FileStorePath = String.Format("{0}/{1}", FileStoreContentPatchPath, PatchFile.FileName).Replace('\\', '/'); string LocalHashString = "<no such file>"; string TransferTempFilePath = LocalPath + ".patchxfer"; if (File.Exists(LocalPath)) { LocalHashString = GetFileChecksum(LocalPath, MD5Csp); Matched = (LocalHashString.ToString() == PatchFile.Checksum.ToLower()); } if (Matched) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} is up to date (checksum {1}).", PatchFile.FileName, LocalHashString)); } else { if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2}.", e, TransferTempFilePath, PatchFile.FileName)); } } Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} needs to be updated (local checksum {1}, remote checksum {2}). Copying file...", PatchFile.FileName, LocalHashString, PatchFile.Checksum)); if (PatchFile.RecompileModule) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} requires a module recompile, flagging module for recompilation.", PatchFile.FileName)); RecompileModule = true; } if (!SentNotification) { Script.SendInfrastructureIrcMessage(String.Format( "Server '{0}' is applying a content patch, and will restart shortly.", Script.GetName(Script.GetModule()))); SentNotification = true; } Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is updating content file '{1}'.", Script.GetName(Script.GetModule()), PatchFile.FileName)); // // The file needs to be updated. Copy it over and // re-validate the checksum. If the checksum did not // match, log an error and delete the file. // try { try { // // Try first to download via the file store // provider. If that fails (e.g. the file // store is not supported), then fall back to // the traditional remote vault share transfer // mechanism. // try { DownloadContentPatchFromFileStore(FileStorePath, TransferTempFilePath, ConnectionString, Script); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.ProcessContentPatches: Couldn't retrieve uncompressed file {0} from Azure, falling back to file share, due to exception: {1}", FileStorePath, e)); File.Copy(RemotePath, TransferTempFilePath, true); } } catch { throw; } UpdateMethod = PatchFile.UpdateMethod; // // If we are patching a hak, rename it away so that // the file can be written to. // switch (UpdateMethod) { case FileUpdateMethod.FileUpdateMethodRename: if (File.Exists(LocalPath)) { string OldFileName = LocalPath + ".old"; if (File.Exists(OldFileName)) { File.Delete(OldFileName); } File.Move(LocalPath, OldFileName); Rename = true; } break; case FileUpdateMethod.FileUpdateMethodDirectReplace: break; } Database.ACR_IncrementStatistic("CONTENT_PATCH_" + PatchFile.Location.ToUpper()); try { if (Rename) { File.Move(TransferTempFilePath, LocalPath); } else { File.Copy(TransferTempFilePath, LocalPath, true); } } catch { if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) { File.Delete(LocalPath); } } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } throw; } if (GetFileChecksum(LocalPath, MD5Csp) != PatchFile.Checksum.ToLower()) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Content patch file {0} was copied, but checksum did not match {1}! This may indicate a configuration error in the database, or file corruption in transit.", PatchFile.FileName, PatchFile.Checksum)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had checksum mismatch for content patch file {1} after hotfix file deployment, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); if (UpdateMethod == FileUpdateMethod.FileUpdateMethodRename) { string OldFileName = LocalPath + ".old"; try { if (File.Exists(LocalPath)) { File.Delete(LocalPath); } } catch { } File.Move(OldFileName, LocalPath); } else { File.Delete(LocalPath); } Database.ACR_IncrementStatistic("CONTENT_PATCH_INCORRECT_CHECKSUM"); } else { ContentChanged = true; Database.ACR_IncrementStatistic("CONTENT_PATCH_UPDATED_FILE"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Successfully updated content patch file {0}.", PatchFile.FileName)); } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Exception {0} updating content patch file {1}.", e, PatchFile.FileName)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception deploying content patch file {1}, rolling back file.", Script.GetName(Script.GetModule()), PatchFile.FileName)); } } if (File.Exists(TransferTempFilePath)) { try { File.Delete(TransferTempFilePath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} removing transfer temp file {1} for transfer file {2} after patching completed.", e, TransferTempFilePath, PatchFile.FileName)); } } } } // // Update autodownloader configuration, as necessary. // try { if (ProcessModuleDownloaderResourcesUpdates(Database, Script)) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: Autodownloader configuration updated."); ContentChanged = true; } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.ProcessContentPatches: Warning: Exception {0} updating autodownloader configuration.", e)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' encountered exception updating autodownloader configuration.", Script.GetName(Script.GetModule()))); } if (ContentChanged) { if (RecompileModule) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.ProcessContentPatches: A module recompile is required; recompiling module..."); CompileModuleScripts(Script, Database); } Database.ACR_IncrementStatistic("CONTENT_PATCH_REBOOT"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' restarting after content patch deployment (old HAK ACR version {1} build date {2}, old module ACR version {3}).", Script.GetName(Script.GetModule()), Database.ACR_GetHAKVersion(), Database.ACR_GetHAKBuildDate(), Database.ACR_GetVersion())); } return(ContentChanged); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { string PlayerName = Player.Name; if (!SystemInfo.IsSafeFileName(PlayerName)) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Invalid player name '{0}'.", PlayerName)); return; } string VaultPath = SystemInfo.GetServerVaultPathForAccount(PlayerName); if (VaultPath == null) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Could not resolve vault path for player '{0}'.", Player.PlayerName)); return; } VaultPath += CharacterFileName; if (!File.Exists(VaultPath)) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Character file '{0}' for player '{1}' was not locally cached.", CharacterFileName, Player.PlayerName)); return; } try { File.Delete(VaultPath); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Exception '{0}' removing cached character '{1}' for player '{2}'.", e, CharacterFileName, Player.PlayerName)); return; } try { if (SystemInfo.GetVaultStoragePluginInUse()) { VaultPath = SystemInfo.GetCentralVaultPathForAccount(PlayerName); VaultPath += CharacterFileName; if (File.Exists(VaultPath)) { File.Delete(VaultPath); } } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Exception '{0}' removing cached character from Azure vault temporary local storage '{1}' for player '{2}'.", e, CharacterFileName, Player.PlayerName)); return; } Script.WriteTimestampedLogEntry(String.Format( "PurgeCachedCharacterEvent.DispatchEvent: Removed cached character '{0}' from player '{1}' vault cache.", CharacterFileName, Player.PlayerName)); }
/// <summary> /// Dispatch the event (in a script context). /// </summary> /// <param name="Script">Supplies the script object.</param> /// <param name="Database">Supplies the database connection.</param> public void DispatchEvent(ACR_ServerCommunicator Script, ALFA.Database Database) { Script.WriteTimestampedLogEntry("ACR_ServerCommunicator: " + Message); }
/// <summary> /// This when a cross-server ping response is received. Its purpose is /// to compute the channel latency and send a message to that effect to /// the requesting player. /// </summary> /// <param name="SourceServerId">Supplies the server id of the server /// that completed the ping request.</param> /// <param name="Arguments">Supplies the serialized state arguments /// string.</param> /// <param name="Script">Supplies the current script object.</param> /// <returns>TRUE on success, else FALSE on failure.</returns> public static int HandleServerPingResponse(int SourceServerId, string Arguments, ACR_ServerCommunicator Script) { int Tick = Environment.TickCount; try { PingState RemoteState; // // Deserialize the remote ping state. If null, a protocol // violation has occurred. // if ((RemoteState = PingState.FromString(Arguments)) == null) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.ServerLatencyMeasurer.HandleServerPingResponse({0}, {1}): Invalid request.", SourceServerId, Arguments)); return(ACR_ServerCommunicator.FALSE); } string ServerName = Script.GetServerName(SourceServerId); if (ServerName == null) { return(ACR_ServerCommunicator.FALSE); } Script.SendMessageToPC(RemoteState.PCObjectId, String.Format( "IPC channel latency to {0}: {1}ms", ServerName, Tick - RemoteState.TickCount)); } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format( "ACR_ServerCommunicator.ServerLatencyMeasurer.HandleServerPingResponse({0}, {1}): Exception: {0}", SourceServerId, Arguments, e)); return(ACR_ServerCommunicator.FALSE); } return(ACR_ServerCommunicator.TRUE); }
/// <summary> /// This method is called when a portal transition has been committed /// to send a player to a remote server. /// </summary> /// <param name="State">Supplies the local state for the player that is /// committed to a portal transition. /// </param> /// <param name="Server">Supplies the destination server for the portal /// event.</param> /// <param name="Script">Supplies the script object.</param> public static void SendGUIStateToServer(PlayerState State, GameServer Server, ACR_ServerCommunicator Script) { // // Build the resynchronization command. The resynchronization // command is parsed by HandleGUIResync(). Note that the remote // and local servers may not be of the same version, i.e. the // remote server may not understand fields that are created by the // local server if it is of a newer version. // ResyncState ResyncInfo = new ResyncState(State.PlayerId, 0); if (State.ChatSelectGUIExpanded) { ResyncInfo.ResyncFlags |= RESYNC_FLAG_CHAT_SELECT_EXPANDED; } // // Dispatch the resync script execution request to the remote // server. The remote server will process it when the request has // been received. // Script.RunScriptOnServer(Server.ServerId, "acr_resync_gui", ResyncInfo.ToString()); }
/// <summary> /// Recompile all scripts in the module. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="Database">Supplies the database connection.</param> private static void CompileModuleScripts(ACR_ServerCommunicator Script, ALFA.Database Database) { ALFA.ScriptCompiler.CompilerResult Result; string CompilerOptions = Script.GetLocalString(Script.GetModule(), "ACR_MOD_COMPILER_OPTIONS"); List <string> CompilerOutput = new List <string>(); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Compiling module scripts with compiler options '{0}'...", CompilerOptions)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' is recompiling module scripts.", Script.GetName(Script.GetModule()))); Result = ALFA.ScriptCompiler.CompileScript("*.nss", CompilerOptions, delegate(string Line) { if (!String.IsNullOrWhiteSpace(Line)) { Script.WriteTimestampedLogEntry(Line); } return(false); }); foreach (string Message in Result.Warnings) { try { CompilerOutput.Add(Message); } catch (Exception) { } } if (Result.Compiled) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.CompileModuleScripts: Module successfully recompiled."); Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE"); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' successfully recompiled module with {1} warning(s) for content patch deployment.", Script.GetName(Script.GetModule()), Result.Warnings.Count)); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: {0} error(s) compiling module!", Result.Errors.Count)); Script.SendInfrastructureDiagnosticIrcMessage(String.Format( "Server '{0}' had {1} error(s), {2} warning(s) recompiling module for content patch deployment.", Script.GetName(Script.GetModule()), Result.Errors.Count, Result.Warnings.Count)); foreach (string Message in Result.Errors) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Error '{0}'.", Message)); try { CompilerOutput.Add(Message); } catch (Exception) { } } Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE_FAILED"); } // // Save compiler output to a temporary file for later retrieval. // try { string FileName = String.Format("{0}{1}ALFAModuleRecompile.log", Path.GetTempPath(), Path.DirectorySeparatorChar); File.WriteAllLines(FileName, CompilerOutput); } catch (Exception) { } }
/// <summary> /// Download a content patch file from a file store. Currently, Azure /// file stores are assumed. Both compressed and uncompressed versions /// of the file are tried in respective order. /// </summary> /// <param name="FileStorePath">Supplies the remote file name that /// designates the file to download.</param> /// <param name="LocalFileName">Supplies the local file name to /// download to.</param> /// <param name="ConnectionString">Supplies the file store connection /// string.</param> /// <param name="Script">Supplies the script object.</param> private static void DownloadContentPatchFromFileStore(string FileStorePath, string LocalFileName, string ConnectionString, ACR_ServerCommunicator Script) { if (String.IsNullOrEmpty(ConnectionString)) { throw new NotSupportedException(); } // // Initialize the file store provider. // FileStore UpdaterStore = FileStoreProvider.CreateAzureFileStore(ConnectionString); FileStoreContainer UpdaterContainer = UpdaterStore.GetContainerReference(FileStoreNamespace.ACRUpdater); FileStoreFile UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath + ".gzip"); // // First attempt to retrieve a gzip compressed version of the file // to patch. If that fails then fall back to a plaintext version. // try { using (MemoryStream MemStream = new MemoryStream()) { UpdaterFile.Read(MemStream); MemStream.Position = 0; using (FileStream OutStream = File.Create(LocalFileName)) { using (GZipStream CompressedStream = new GZipStream(MemStream, CompressionMode.Decompress)) { CompressedStream.CopyTo(OutStream); } } } } catch (Exception e) { Script.WriteTimestampedLogEntry(String.Format("ModuleContentPatcher.DownloadContentPatchFromFileStore: Couldn't retrieve compressed file {0} from Azure, trying uncompressed file, due to exception: {1}", FileStorePath, e)); UpdaterFile = UpdaterContainer.GetFileReference(FileStorePath); using (FileStream OutStream = File.Create(LocalFileName)) { UpdaterFile.Read(OutStream); } } }
/// <summary> /// Recompile all scripts in the module. /// </summary> /// <param name="Script">Supplies the main script object.</param> /// <param name="Database">Supplies the database connection.</param> private static void CompileModuleScripts(ACR_ServerCommunicator Script, ALFA.Database Database) { ALFA.ScriptCompiler.CompilerResult Result; string CompilerOptions = Script.GetLocalString(Script.GetModule(), "ACR_MOD_COMPILER_OPTIONS"); Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Compiling module scripts with compiler options '{0}'...", CompilerOptions)); Result = ALFA.ScriptCompiler.CompileScript("*.nss", CompilerOptions, delegate(string Line) { Script.WriteTimestampedLogEntry(Line); return false; }); if (Result.Compiled) { Script.WriteTimestampedLogEntry("ModuleContentPatcher.CompileModuleScripts: Module successfully recompiled."); Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE"); } else { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: {0} error(s) compiling module!", Result.Errors.Count)); foreach (string Message in Result.Errors) { Script.WriteTimestampedLogEntry(String.Format( "ModuleContentPatcher.CompileModuleScripts: Error '{0}'.", Message)); } Database.ACR_IncrementStatistic("CONTENT_PATCH_RECOMPILE_FAILED"); } }