/// <summary> /// Logs the client out of the game client, and closes the stream /// </summary> /// <param name="reason"> /// The disconnect reason code. /// </param> /// <remarks> /// If set the <paramref name="reason"/> is set to <see cref="DisconnectReason.ForcedServerShutdown"/>, /// the OnDisconect event will not be called, the database will not be updated to reset everyone's session code, /// and the EventArgs objects will NOT be returned to the IO pool. You should only set to /// <see cref="DisconnectReason.ForcedServerShutdown"/> for a planned server shutdown. /// </remarks> public void Disconnect(DisconnectReason reason) { // Update database session if (Status == LoginStatus.Completed && reason != DisconnectReason.ForcedServerShutdown) { try { using (GamespyDatabase Database = new GamespyDatabase()) Database.Execute("UPDATE player SET online=0 WHERE id=" + PlayerId); } catch { } } // If connection is still alive, disconnect user try { Stream.OnDisconnect -= Stream_OnDisconnect; Stream.DataReceived -= Stream_DataReceived; Stream.Close(reason == DisconnectReason.ForcedServerShutdown); } catch { } // Set status and log if (Status == LoginStatus.Completed) { if (reason == DisconnectReason.NormalLogout) { ServerManager.Log( "Client Logout: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint ); } else { ServerManager.Log( "Client Disconnected: {0} - {1} - {2}, Code={3}", PlayerNick, PlayerId, RemoteEndPoint, Enum.GetName(typeof(DisconnectReason), reason) ); } } // Preapare to be unloaded from memory Status = LoginStatus.Disconnected; Dispose(); // Call disconnect event OnDisconnect?.Invoke(this); }
/// <summary> /// Starts the Login Server listeners, and begins accepting new connections /// </summary> public static void StartServers() { // Make sure we arent already running! if (isRunning) { return; } try { // Start the DB Connection Console.Write("Connecting to Mysql... "); using (GamespyDatabase Database = new GamespyDatabase()) { Console.Write("Success!" + Environment.NewLine); // Reset game sessions if (Config.GetType <bool>("Settings", "ResetGameSessionsOnStartup")) { Console.Write("Resetting all game sessions... "); Database.Execute("UPDATE web_users SET game_session=0 WHERE id > 0"); Console.Write("Success!" + Environment.NewLine); } } // Create our end point to bind to int port = Config.GetType <int>("Settings", "LoginServerPort"); IPAddress address = IPAddress.Parse(Config.GetValue("Settings", "ServerBindIp")); // Start the Client Manager Server Console.Write("<GPCM> Binding to TCP port {0}... ", port); CmServer = new GpcmServer(new IPEndPoint(address, port)); Console.Write("Success!" + Environment.NewLine); // Start Search Provider Server Console.Write("<GPSP> Binding to TCP port {0}... ", ++port); SpServer = new GpspServer(new IPEndPoint(address, port)); Console.Write("Success!" + Environment.NewLine); } catch { Console.Write("Failed!" + Environment.NewLine); throw; } // Let the client know we are ready for connections isRunning = true; }
/// <summary> /// Logs the client out of the game client, and closes the stream /// </summary> /// <param name="where"> /// The disconnect code. If set to 9, the OnDisconect event will not be called, the database /// will not be updated to reset everyone's session code, and the EventArgs objects will NOT /// be returned to the IO pool. You should only set to 9 for a planned server shutdown. /// </param> /// <remarks> /// Codes: /// 0 => Client sends the "logout" command /// 1 => Keep Alive Packet failed to send (may not work with new async socket code) /// 2 => Invalid login query, or username was incorrect /// 3 => Incorrect Password /// 4 => An error occured while trying to login the client (could be database related) /// 5 => Cant create account, username exists already /// 6 => Error Creating new account in database /// 7 => Invalid query for account creation, or an exception was thrown while trying to create account /// 8 => Stream Disconnected /// 9 => Forced server shutdown [No events called, database sessions are not updated, and EventArgs are disposed] /// 10 => Client challenge already sent /// </remarks> public void Disconnect(int where) { // Update database session if (Status == LoginStatus.Completed && where < 9) { try { using (GamespyDatabase Database = new GamespyDatabase()) Database.Execute("UPDATE web_users SET game_session=0 WHERE pid=" + PlayerId); } catch { } } // If connection is still alive, disconnect user try { Stream.OnDisconnect -= Stream_OnDisconnect; Stream.DataReceived -= Stream_DataReceived; Stream.Close(where == 9); } catch { } // Set status and log if (Status == LoginStatus.Completed) { ServerManager.Log("Client Logout: {0} - {1} - {2}, Code={3}", PlayerNick, PlayerId, RemoteEndPoint, where); } // Preapare to be unloaded from memory Status = LoginStatus.Disconnected; Dispose(); // Call disconnect event if (OnDisconnect != null) { OnDisconnect(this); } }
/// <summary> /// This method verifies the login information sent by /// the client, and returns encrypted data for the client /// to verify as well /// </summary> public void ProcessLogin(Dictionary <string, string> Recv) { // Make sure we have all the required data to process this login if (!Recv.ContainsKey("uniquenick") || !Recv.ContainsKey("challenge") || !Recv.ContainsKey("response")) { Stream.SendAsync(@"\error\\err\0\fatal\\errmsg\Invalid Query!\id\1\final\"); Disconnect(2); return; } // Dispose connection after use try { using (GamespyDatabase Conn = new GamespyDatabase()) { // Try and fetch the user from the database Dictionary <string, object> User = Conn.GetUser(Recv["uniquenick"]); if (User == null) { Stream.SendAsync(@"\error\\err\265\fatal\\errmsg\The uniquenick provided is incorrect!\id\1\final\"); Disconnect(2); return; } // Set player variables PlayerId = Int32.Parse(User["pid"].ToString()); PlayerNick = Recv["uniquenick"]; PlayerEmail = User["email"].ToString(); PlayerCountryCode = User["game_country"].ToString(); PasswordHash = User["password"].ToString().ToLowerInvariant(); // Use the GenerateProof method to compare with the "response" value. This validates the given password if (Recv["response"] == GenerateProof(Recv["challenge"], ServerChallengeKey)) { // Create session key SessionKey = Crc.ComputeChecksum(PlayerNick); // Password is correct Stream.SendAsync( @"\lc\2\sesskey\{0}\proof\{1}\userid\{2}\profileid\{2}\uniquenick\{3}\lt\{4}__\id\1\final\", SessionKey, GenerateProof(ServerChallengeKey, Recv["challenge"]), // Do this again, Params are reversed! PlayerId, PlayerNick, GenerateRandomString(22) // Generate LT whatever that is (some sort of random string, 22 chars long) ); // Log Incoming Connections ServerManager.Log("Client Login: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint); Conn.Execute( "UPDATE web_users SET last_game_ip=@P0, game_session=1, game_tstamp=@P1 WHERE pid=@P2", RemoteEndPoint.Address, DateTime.UtcNow.ToUnixTimestamp(), PlayerId ); // Update status last, and call success login Status = LoginStatus.Completed; if (OnSuccessfulLogin != null) { OnSuccessfulLogin(this); } } else { // Log Incoming Connections ServerManager.Log("Failed Login Attempt: {0} - {1} - {2}", PlayerNick, PlayerId, RemoteEndPoint); // Password is incorrect with database value Stream.SendAsync(@"\error\\err\260\fatal\\errmsg\The password provided is incorrect.\id\1\final\"); Disconnect(3); } } } catch (Exception ex) { ExceptionHandler.GenerateExceptionLog(ex); Disconnect(4); return; } }