diff --git a/Source/ACE.Adapter/ACE.Adapter.csproj b/Source/ACE.Adapter/ACE.Adapter.csproj index 18a9d190dd..83fa242831 100644 --- a/Source/ACE.Adapter/ACE.Adapter.csproj +++ b/Source/ACE.Adapter/ACE.Adapter.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0 AnyCPU 1.0.0 ACEmulator Contributors diff --git a/Source/ACE.Database/ACE.Database.csproj b/Source/ACE.Database/ACE.Database.csproj index 912b4bc661..4cd5e21ea0 100644 --- a/Source/ACE.Database/ACE.Database.csproj +++ b/Source/ACE.Database/ACE.Database.csproj @@ -1,7 +1,7 @@ - netstandard2.0 + netstandard2.0 AnyCPU ACEmulator Contributors git @@ -22,13 +22,6 @@ runtime; build; native; contentfiles; analyzers - - - - - - - diff --git a/Source/ACE.Server/Network/Handlers/AuthenticationHandler.cs b/Source/ACE.Server/Network/Handlers/AuthenticationHandler.cs index 8694c8e8ed..886dd147fc 100644 --- a/Source/ACE.Server/Network/Handlers/AuthenticationHandler.cs +++ b/Source/ACE.Server/Network/Handlers/AuthenticationHandler.cs @@ -87,7 +87,6 @@ private static void DoLogin(Session session, PacketInboundLoginRequest loginRequ try { - log.Debug($"new client connected: {loginRequest.Account}. setting session properties"); AccountSelectCallback(account, session, loginRequest); } catch (Exception ex) @@ -97,18 +96,9 @@ private static void DoLogin(Session session, PacketInboundLoginRequest loginRequ } } - - private static void AccountSelectCallback(Account account, Session session, PacketInboundLoginRequest loginRequest) + private static void SendConnectRequest(Session session) { - packetLog.DebugFormat("ConnectRequest TS: {0}", Timers.PortalYearTicks); - - if (session.Network.ConnectionData.ServerSeed == null || session.Network.ConnectionData.ClientSeed == null) - { - // these are null if ConnectionData.DiscardSeeds() is called because of some other error condition. - session.Terminate(SessionTerminationReason.BadHandshake, new GameMessageCharacterError(CharacterError.ServerCrash1)); - return; - } - + // verify: should this happen if server responds with connection error? var connectRequest = new PacketOutboundConnectRequest( Timers.PortalYearTicks, session.Network.ConnectionData.ConnectionCookie, @@ -119,15 +109,27 @@ private static void AccountSelectCallback(Account account, Session session, Pack session.Network.ConnectionData.DiscardSeeds(); session.Network.EnqueueSend(connectRequest); + } + + + private static void AccountSelectCallback(Account account, Session session, PacketInboundLoginRequest loginRequest) + { + packetLog.DebugFormat("ConnectRequest TS: {0}", Timers.PortalYearTicks); + + if (session.State != SessionState.WorldConnected && (session.Network.ConnectionData.ServerSeed == null || session.Network.ConnectionData.ClientSeed == null)) + { + // these are null if ConnectionData.DiscardSeeds() is called because of some other error condition. + session.Terminate(SessionTerminationReason.BadHandshake, new GameMessageCharacterError(CharacterError.ServerCrash1)); + return; + } if (loginRequest.NetAuthType < NetAuthType.AccountPassword) { if (loginRequest.Account == "acservertracker:jj9h26hcsggc") { //log.Info($"Incoming ping from a Thwarg-Launcher client... Sending Pong..."); - + SendConnectRequest(session); session.Terminate(SessionTerminationReason.PongSentClosingConnection, new GameMessageCharacterError(CharacterError.ServerCrash1)); - return; } @@ -136,6 +138,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack else log.Debug($"client {loginRequest.Account} connected with no Password or GlsTicket included so booting"); + SendConnectRequest(session); session.Terminate(SessionTerminationReason.NotAuthorizedNoPasswordOrGlsTicketIncludedInLoginReq, new GameMessageCharacterError(CharacterError.AccountInvalid)); return; @@ -143,6 +146,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack if (account == null) { + SendConnectRequest(session); session.Terminate(SessionTerminationReason.NotAuthorizedAccountNotFound, new GameMessageCharacterError(CharacterError.AccountDoesntExist)); return; } @@ -151,6 +155,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack { if (NetworkManager.Find(account.AccountName) != null) { + SendConnectRequest(session); session.Terminate(SessionTerminationReason.AccountInUse, new GameMessageCharacterError(CharacterError.Logon)); return; } @@ -165,6 +170,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack else log.Debug($"client {loginRequest.Account} connected with non matching password so booting"); + SendConnectRequest(session); session.Terminate(SessionTerminationReason.NotAuthorizedPasswordMismatch, new GameMessageBootAccount(" because the password entered for this account was not correct.")); // TO-DO: temporary lockout of account preventing brute force password discovery @@ -179,10 +185,15 @@ private static void AccountSelectCallback(Account account, Session session, Pack if (previouslyConnectedAccount != null) { + // do not send connection request here, or else vials will fill up on new client, + // and it won't try to repeatedly reconnect until previous char is logged out of world previouslyConnectedAccount.Terminate(SessionTerminationReason.AccountLoggedIn, new GameMessageCharacterError(CharacterError.Logon)); + return; } } + log.Debug($"new client connected: {loginRequest.Account}. setting session properties"); + if (WorldManager.WorldStatus == WorldManager.WorldStatusState.Open) log.Info($"client {loginRequest.Account} connected with verified password"); else @@ -195,6 +206,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack else log.Debug($"client {loginRequest.Account} connected with GlsTicket which is not implemented yet so booting"); + SendConnectRequest(session); session.Terminate(SessionTerminationReason.NotAuthorizedGlsTicketNotImplementedToProcLoginReq, new GameMessageCharacterError(CharacterError.AccountInvalid)); return; @@ -206,6 +218,7 @@ private static void AccountSelectCallback(Account account, Session session, Pack if (now < account.BanExpireTime.Value) { var reason = account.BanReason; + SendConnectRequest(session); session.Terminate(SessionTerminationReason.AccountBanned, new GameMessageBootAccount($"{(reason != null ? $" - {reason}" : null)}"), null, reason); return; } @@ -215,9 +228,12 @@ private static void AccountSelectCallback(Account account, Session session, Pack } } + SendConnectRequest(session); + account.UpdateLastLogin(session.EndPoint.Address); session.SetAccount(account.AccountId, account.AccountName, (AccessLevel)account.AccessLevel); + session.State = SessionState.AuthConnectResponse; } diff --git a/Source/ACE.Server/Network/Managers/NetworkManager.cs b/Source/ACE.Server/Network/Managers/NetworkManager.cs index 05e36c618c..5e1fff2417 100644 --- a/Source/ACE.Server/Network/Managers/NetworkManager.cs +++ b/Source/ACE.Server/Network/Managers/NetworkManager.cs @@ -117,6 +117,17 @@ public static void ProcessPacket(ConnectionListener connectionListener, ClientPa var ipAllowsUnlimited = ConfigManager.Config.Server.Network.AllowUnlimitedSessionsFromIPAddresses.Contains(endPoint.Address.ToString()); if (ipAllowsUnlimited || ConfigManager.Config.Server.Network.MaximumAllowedSessionsPerIPAddress == -1 || GetSessionEndpointTotalByAddressCount(endPoint.Address) < ConfigManager.Config.Server.Network.MaximumAllowedSessionsPerIPAddress) { + // if a character from a previous session is still logged into the world, + // we really want FindOrCreateSession to return the previous session here + // if the previous session is dropped immediately, before the character has completely exited world, + // in-game packets from the previous character will start broadcasting to client @ char select screen, + // which can cause ac client to get into weird state. + + // for example, if the server responds with 'character already in world' when character tries to log in again, + // if in-game packets from previous session are broadcast @ char select screen, + // it causes client to go into an automatic continuous loop very quickly, + // where the client tries to repeatedly re-enter the world + var session = FindOrCreateSession(connectionListener, endPoint); if (session != null) { diff --git a/Source/ACE.Server/Network/Session.cs b/Source/ACE.Server/Network/Session.cs index d7607b9ec4..d1dc2b7ab1 100644 --- a/Source/ACE.Server/Network/Session.cs +++ b/Source/ACE.Server/Network/Session.cs @@ -66,8 +66,10 @@ public Session(ConnectionListener connectionListener, IPEndPoint endPoint, ushor private bool CheckState(ClientPacket packet) { - if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest) && State != SessionState.AuthLoginRequest) - return false; + // if existing session was found in WorldConnected state, it should be pushed through for AccountSelectCallback() to terminate + + //if (packet.Header.HasFlag(PacketHeaderFlags.LoginRequest) && State != SessionState.AuthLoginRequest) + //return false; if (packet.Header.HasFlag(PacketHeaderFlags.ConnectResponse) && State != SessionState.AuthConnectResponse) return false; @@ -249,11 +251,14 @@ public void Terminate(SessionTerminationReason reason, GameMessage message = nul { Network.EnqueueSend(message); } - PendingTermination = new SessionTerminationDetails() + if (PendingTermination == null) { - ExtraReason = extraReason, - Reason = reason - }; + PendingTermination = new SessionTerminationDetails() + { + ExtraReason = extraReason, + Reason = reason + }; + } } public void DropSession() @@ -283,7 +288,12 @@ public void DropSession() // At this point, if the player was on a landblock, they'll still exist on that landblock until the logout animation completes (~6s). } + else + DropSessionPost(); + } + public void DropSessionPost() + { NetworkManager.RemoveSession(this); // This is a temp fix to mark the Session.Network portion of the Session as released diff --git a/Source/ACE.Server/WorldObjects/Player.cs b/Source/ACE.Server/WorldObjects/Player.cs index c8faad5415..b3ecd781f1 100644 --- a/Source/ACE.Server/WorldObjects/Player.cs +++ b/Source/ACE.Server/WorldObjects/Player.cs @@ -559,6 +559,9 @@ public void LogOut_Inner(bool clientSessionTerminatedAbruptly = false) SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); + + if (Session.PendingTermination != null || ServerManager.ShutdownInProgress) + Session.DropSessionPost(); }); // close any open landblock containers (chests / corpses) @@ -592,6 +595,9 @@ public void LogOut_Inner(bool clientSessionTerminatedAbruptly = false) SetPropertiesAtLogOut(); SavePlayerToDatabase(); PlayerManager.SwitchPlayerFromOnlineToOffline(this); + + if (Session.PendingTermination != null || ServerManager.ShutdownInProgress) + Session.DropSessionPost(); } }