🆙 Stage 1 reconnect

This commit is contained in:
duckietm
2026-03-20 16:08:51 +01:00
parent 349b2c4dcd
commit 5807303807
5 changed files with 286 additions and 54 deletions
@@ -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()));
@@ -26,6 +26,7 @@ public class GameClient {
private Habbo habbo;
private boolean handshakeFinished;
private String machineId = "";
private String ssoTicket = "";
public final ConcurrentHashMap<Integer, Integer> incomingPacketCounter = new ConcurrentHashMap<>(25);
public final ConcurrentHashMap<Class<? extends MessageHandler>, 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;
@@ -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<Habbo> getHabbosWithMachineId(String machineId) {
List<Habbo> habbos = new ArrayList<>();
@@ -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<Integer, GhostSession> 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;
}
}
}
@@ -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("<br/>")));
} 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("<br/>")));
} 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);