From 5807303807f62e5b7f58e97f0a348c8089818162 Mon Sep 17 00:00:00 2001 From: duckietm Date: Fri, 20 Mar 2026 16:08:51 +0100 Subject: [PATCH] =?UTF-8?q?=F0=9F=86=99=20Stage=201=20reconnect?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/com/eu/habbo/Emulator.java | 2 + .../habbohotel/gameclients/GameClient.java | 20 ++- .../gameclients/GameClientManager.java | 16 ++ .../gameclients/SessionResumeManager.java | 137 +++++++++++++++ .../incoming/handshake/SecureLoginEvent.java | 165 ++++++++++++------ 5 files changed, 286 insertions(+), 54 deletions(-) create mode 100644 Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java diff --git a/Emulator/src/main/java/com/eu/habbo/Emulator.java b/Emulator/src/main/java/com/eu/habbo/Emulator.java index 455cb540..11f29086 100644 --- a/Emulator/src/main/java/com/eu/habbo/Emulator.java +++ b/Emulator/src/main/java/com/eu/habbo/Emulator.java @@ -7,6 +7,7 @@ import com.eu.habbo.core.*; import com.eu.habbo.core.consolecommands.ConsoleCommand; import com.eu.habbo.database.Database; import com.eu.habbo.habbohotel.GameEnvironment; +import com.eu.habbo.habbohotel.gameclients.SessionResumeManager; import com.eu.habbo.networking.gameserver.GameServer; import com.eu.habbo.networking.rconserver.RCONServer; import com.eu.habbo.plugin.PluginManager; @@ -319,6 +320,7 @@ public final class Emulator { if (Emulator.pluginManager != null) tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStartShutdownEvent())); if (Emulator.rconServer != null) tryShutdown(() -> Emulator.rconServer.stop()); + tryShutdown(() -> SessionResumeManager.getInstance().disposeAll()); if (Emulator.gameEnvironment != null) tryShutdown(() -> Emulator.gameEnvironment.dispose()); if (Emulator.pluginManager != null) tryShutdown(() -> Emulator.pluginManager.fireEvent(new EmulatorStoppedEvent())); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java index 61c219cc..f526c15b 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClient.java @@ -26,6 +26,7 @@ public class GameClient { private Habbo habbo; private boolean handshakeFinished; private String machineId = ""; + private String ssoTicket = ""; public final ConcurrentHashMap incomingPacketCounter = new ConcurrentHashMap<>(25); public final ConcurrentHashMap, Long> messageTimestamps = new ConcurrentHashMap<>(); @@ -82,6 +83,14 @@ public class GameClient { this.machineId = machineId; } + public String getSsoTicket() { + return this.ssoTicket; + } + + public void setSsoTicket(String ssoTicket) { + this.ssoTicket = ssoTicket != null ? ssoTicket : ""; + } + public void sendResponse(MessageComposer composer) { this.sendResponse(composer.compose()); } @@ -145,8 +154,15 @@ public class GameClient { if (this.habbo != null) { if (this.habbo.isOnline()) { - this.habbo.getHabboInfo().setOnline(false); - this.habbo.disconnect(); + // Try to park the habbo in the grace period instead of immediate disconnect + boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket); + + if (!parked) { + // No grace period configured — immediate disconnect as before + this.habbo.getHabboInfo().setOnline(false); + this.habbo.disconnect(); + } + // If parked, do NOT call disconnect() — the habbo stays in the room } this.habbo = null; diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java index 68366a0a..cd0602cb 100644 --- a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/GameClientManager.java @@ -116,6 +116,22 @@ public class GameClientManager { } + /** + * Find an existing GameClient that authenticated with the given SSO ticket. + * Used to detect reconnections where the old connection hasn't been closed yet. + */ + public GameClient findClientBySsoTicket(String ssoTicket) { + if (ssoTicket == null || ssoTicket.isEmpty()) return null; + + for (GameClient client : this.clients.values()) { + if (ssoTicket.equals(client.getSsoTicket()) && client.getHabbo() != null) { + return client; + } + } + return null; + } + + public List getHabbosWithMachineId(String machineId) { List habbos = new ArrayList<>(); diff --git a/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java new file mode 100644 index 00000000..61fa6085 --- /dev/null +++ b/Emulator/src/main/java/com/eu/habbo/habbohotel/gameclients/SessionResumeManager.java @@ -0,0 +1,137 @@ +package com.eu.habbo.habbohotel.gameclients; + +import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.users.Habbo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ScheduledFuture; + +public class SessionResumeManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(SessionResumeManager.class); + + private static SessionResumeManager instance; + + private final ConcurrentHashMap ghostSessions = new ConcurrentHashMap<>(); + + public static SessionResumeManager getInstance() { + if (instance == null) { + instance = new SessionResumeManager(); + } + return instance; + } + + public int getGracePeriodSeconds() { + return Emulator.getConfig().getInt("session.reconnect.grace.seconds", 30); + } + + public boolean parkHabbo(Habbo habbo, String ssoTicket) { + int graceSeconds = getGracePeriodSeconds(); + if (graceSeconds <= 0) { + return false; + } + + int userId = habbo.getHabboInfo().getId(); + + GhostSession existing = ghostSessions.remove(userId); + if (existing != null && existing.disposeFuture != null) { + existing.disposeFuture.cancel(false); + } + + LOGGER.info("[SessionResume] Parking {} (id={}) for {}s grace period", + habbo.getHabboInfo().getUsername(), userId, graceSeconds); + + if (ssoTicket != null && !ssoTicket.isEmpty()) { + restoreSsoTicket(userId, ssoTicket); + } + + ScheduledFuture future = Emulator.getThreading().run(() -> { + GhostSession ghost = ghostSessions.remove(userId); + if (ghost != null) { + LOGGER.info("[SessionResume] Grace period expired for {} (id={}) - performing full disconnect", + ghost.habbo.getHabboInfo().getUsername(), userId); + performFullDisconnect(ghost.habbo); + } + }, graceSeconds * 1000); + + ghostSessions.put(userId, new GhostSession(habbo, ssoTicket, future)); + return true; + } + + public Habbo resumeSession(int userId) { + GhostSession ghost = ghostSessions.remove(userId); + if (ghost == null) { + return null; + } + + if (ghost.disposeFuture != null) { + ghost.disposeFuture.cancel(false); + } + + LOGGER.info("[SessionResume] Resuming session for {} (id={})", + ghost.habbo.getHabboInfo().getUsername(), userId); + + return ghost.habbo; + } + + public boolean hasGhostSession(int userId) { + return ghostSessions.containsKey(userId); + } + + public void disposeAll() { + for (GhostSession ghost : ghostSessions.values()) { + if (ghost.disposeFuture != null) { + ghost.disposeFuture.cancel(false); + } + performFullDisconnect(ghost.habbo); + } + ghostSessions.clear(); + } + + private void performFullDisconnect(Habbo habbo) { + try { + habbo.getHabboInfo().setOnline(false); + habbo.disconnect(); + } catch (Exception e) { + LOGGER.error("[SessionResume] Error during deferred disconnect", e); + } + clearSsoTicket(habbo.getHabboInfo().getId()); + } + + private void restoreSsoTicket(int userId, String ssoTicket) { + try (var connection = Emulator.getDatabase().getDataSource().getConnection(); + var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + statement.setString(1, ssoTicket); + statement.setInt(2, userId); + statement.execute(); + LOGGER.info("[SessionResume] Restored SSO ticket for user {} during grace period", userId); + } catch (Exception e) { + LOGGER.error("[SessionResume] Failed to restore SSO ticket for user " + userId, e); + } + } + + private void clearSsoTicket(int userId) { + try (var connection = Emulator.getDatabase().getDataSource().getConnection(); + var statement = connection.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + statement.setString(1, ""); + statement.setInt(2, userId); + statement.execute(); + } catch (Exception e) { + LOGGER.error("[SessionResume] Failed to clear SSO ticket for user " + userId, e); + } + } + + private static class GhostSession { + final Habbo habbo; + final String ssoTicket; + final ScheduledFuture disposeFuture; + + GhostSession(Habbo habbo, String ssoTicket, ScheduledFuture disposeFuture) { + this.habbo = habbo; + this.ssoTicket = ssoTicket; + this.disposeFuture = disposeFuture; + } + } +} diff --git a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java index 00b469ba..e3ae9244 100644 --- a/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java +++ b/Emulator/src/main/java/com/eu/habbo/messages/incoming/handshake/SecureLoginEvent.java @@ -1,11 +1,14 @@ package com.eu.habbo.messages.incoming.handshake; import com.eu.habbo.Emulator; +import com.eu.habbo.habbohotel.gameclients.GameClient; +import com.eu.habbo.habbohotel.gameclients.SessionResumeManager; import com.eu.habbo.habbohotel.messenger.Messenger; import com.eu.habbo.habbohotel.modtool.ModToolSanctionItem; import com.eu.habbo.habbohotel.modtool.ModToolSanctions; import com.eu.habbo.habbohotel.navigation.NavigatorSavedSearch; import com.eu.habbo.habbohotel.permissions.Permission; +import com.eu.habbo.habbohotel.rooms.Room; import com.eu.habbo.habbohotel.rooms.RoomManager; import com.eu.habbo.habbohotel.users.Habbo; import com.eu.habbo.habbohotel.users.HabboManager; @@ -81,31 +84,83 @@ public class SecureLoginEvent extends MessageHandler { } if (this.client.getHabbo() == null) { - Habbo habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); + this.client.setSsoTicket(sso); + + GameClient existingClient = Emulator.getGameServer().getGameClientManager().findClientBySsoTicket(sso); + if (existingClient != null && existingClient != this.client) { + LOGGER.info("[SessionResume] Found existing client with same SSO ticket — disposing old connection to trigger parking"); + Emulator.getGameServer().getGameClientManager().disposeClient(existingClient); + } + + int lookupUserId = 0; + try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); + java.sql.PreparedStatement stmt = conn.prepareStatement("SELECT id FROM users WHERE auth_ticket = ? LIMIT 1")) { + stmt.setString(1, sso); + try (java.sql.ResultSet rs = stmt.executeQuery()) { + if (rs.next()) { + lookupUserId = rs.getInt("id"); + } + } + } catch (Exception e) { + LOGGER.error("Caught exception looking up user for session resume", e); + } + + Habbo habbo = null; + boolean isSessionResume = false; + + if (lookupUserId > 0) { + habbo = SessionResumeManager.getInstance().resumeSession(lookupUserId); + } + if (habbo != null) { - try { - habbo.setClient(this.client); - this.client.setHabbo(habbo); - if(!this.client.getHabbo().connect()) { + isSessionResume = true; + LOGGER.info("[SessionResume] Resuming session for {} (id={})", + habbo.getHabboInfo().getUsername(), habbo.getHabboInfo().getId()); + + habbo.setClient(this.client); + this.client.setHabbo(habbo); + this.client.setMachineId(habbo.getHabboInfo().getMachineID()); + + if (!Emulator.debugging) { + try (java.sql.Connection conn = Emulator.getDatabase().getDataSource().getConnection(); + java.sql.PreparedStatement stmt = conn.prepareStatement("UPDATE users SET auth_ticket = ? WHERE id = ? LIMIT 1")) { + stmt.setString(1, ""); + stmt.setInt(2, habbo.getHabboInfo().getId()); + stmt.execute(); + } catch (Exception e) { + LOGGER.error("Failed to clear SSO ticket after session resume", e); + } + } + } else { + habbo = Emulator.getGameEnvironment().getHabboManager().loadHabbo(sso); + } + + if (habbo != null) { + if (!isSessionResume) { + try { + habbo.setClient(this.client); + this.client.setHabbo(habbo); + if(!this.client.getHabbo().connect()) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } + + if (this.client.getHabbo().getHabboInfo() == null) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } + + if (this.client.getHabbo().getHabboInfo().getRank() == null) { + throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!"); + } + + Emulator.getThreading().run(habbo); + Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo); + } catch (Exception e) { + LOGGER.error("Caught exception", e); Emulator.getGameServer().getGameClientManager().disposeClient(this.client); return; } - - if (this.client.getHabbo().getHabboInfo() == null) { - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; - } - - if (this.client.getHabbo().getHabboInfo().getRank() == null) { - throw new NullPointerException(habbo.getHabboInfo().getUsername() + " has a NON EXISTING RANK!"); - } - - Emulator.getThreading().run(habbo); - Emulator.getGameEnvironment().getHabboManager().addHabbo(habbo); - } catch (Exception e) { - LOGGER.error("Caught exception", e); - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; } if(ClothingValidationManager.VALIDATE_ON_LOGIN) { @@ -121,7 +176,13 @@ public class SecureLoginEvent extends MessageHandler { int roomIdToEnter = 0; - if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0) + if (isSessionResume) { + Room currentRoom = habbo.getHabboInfo().getCurrentRoom(); + if (currentRoom != null) { + LOGGER.info("[SessionResume] {} is still in room {} — client will resume in-place", + habbo.getHabboInfo().getUsername(), currentRoom.getId()); + } + } else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && this.client.getHabbo().getHabboInfo().getHomeRoom() != 0) roomIdToEnter = this.client.getHabbo().getHabboInfo().getHomeRoom(); else if (!this.client.getHabbo().getHabboStats().nux || Emulator.getConfig().getBoolean("retro.style.homeroom") && RoomManager.HOME_ROOM_ID > 0) roomIdToEnter = RoomManager.HOME_ROOM_ID; @@ -152,8 +213,6 @@ public class SecureLoginEvent extends MessageHandler { this.client.sendResponses(messages); - //Hardcoded - //this.client.sendResponse(new ForumsTestComposer()); this.client.sendResponse(new InventoryAchievementsComposer()); ModToolSanctions modToolSanctions = Emulator.getGameEnvironment().getModToolSanctions(); @@ -189,42 +248,44 @@ public class SecureLoginEvent extends MessageHandler { } } - UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin()); - Emulator.getPluginManager().fireEvent(userLoginEvent); + if (!isSessionResume) { + UserLoginEvent userLoginEvent = new UserLoginEvent(habbo, this.client.getHabbo().getHabboInfo().getIpLogin()); + Emulator.getPluginManager().fireEvent(userLoginEvent); - if(userLoginEvent.isCancelled()) { - Emulator.getGameServer().getGameClientManager().disposeClient(this.client); - return; - } + if(userLoginEvent.isCancelled()) { + Emulator.getGameServer().getGameClientManager().disposeClient(this.client); + return; + } - if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) { - final Habbo finalHabbo = habbo; - Emulator.getThreading().run(() -> { - if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) { - SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("
"))); - } else { - SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()))); - } - }, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000)); - } + if (Emulator.getConfig().getBoolean("hotel.welcome.alert.enabled")) { + final Habbo finalHabbo = habbo; + Emulator.getThreading().run(() -> { + if (Emulator.getConfig().getBoolean("hotel.welcome.alert.oldstyle")) { + SecureLoginEvent.this.client.sendResponse(new MessagesForYouComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()).split("
"))); + } else { + SecureLoginEvent.this.client.sendResponse(new GenericAlertComposer(HabboManager.WELCOME_MESSAGE.replace("%username%", finalHabbo.getHabboInfo().getUsername()).replace("%user%", finalHabbo.getHabboInfo().getUsername()))); + } + }, Emulator.getConfig().getInt("hotel.welcome.alert.delay", 5000)); + } - if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) { - SubscriptionHabboClub.processUnclaimed(habbo); - } + if(SubscriptionHabboClub.HC_PAYDAY_ENABLED) { + SubscriptionHabboClub.processUnclaimed(habbo); + } - SubscriptionHabboClub.processClubBadge(habbo); + SubscriptionHabboClub.processClubBadge(habbo); - Messenger.checkFriendSizeProgress(habbo); + Messenger.checkFriendSizeProgress(habbo); - if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) { - habbo.getHabboStats().hasGottenDefaultSavedSearches = true; - Emulator.getThreading().run(habbo.getHabboStats()); + if (!habbo.getHabboStats().hasGottenDefaultSavedSearches) { + habbo.getHabboStats().hasGottenDefaultSavedSearches = true; + Emulator.getThreading().run(habbo.getHabboStats()); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", "")); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", "")); - habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("official-root", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("my", "")); + habbo.getHabboInfo().addSavedSearch(new NavigatorSavedSearch("favorites", "")); - this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches())); + this.client.sendResponse(new NewNavigatorSavedSearchesComposer(this.client.getHabbo().getHabboInfo().getSavedSearches())); + } } } else { Emulator.getGameServer().getGameClientManager().disposeClient(this.client);