fix(session): separate forced disconnects from resume parking

Add a forced dispose path for bans, RCON disconnects, logout/account endpoints, plugin-cancelled login, duplicate login replacement, and late MAC-ban enforcement. Soft channel closes still park a session for reconnect, while security-driven closes now bypass session resume. Also null-guard client/channel disposal and cover the contract with focused tests.
This commit is contained in:
simoleo89
2026-06-09 22:02:07 +02:00
parent d984461cc0
commit 5c0f2d2855
9 changed files with 59 additions and 13 deletions
@@ -149,6 +149,10 @@ public class GameClient {
}
public void dispose() {
this.dispose(true);
}
public void dispose(boolean allowSessionResume) {
try {
this.channel.close();
@@ -161,7 +165,7 @@ public class GameClient {
// appena ripristinata (era la causa del "Bye"/kick al 2° reconnect).
if (this.habbo.getClient() == this && this.habbo.isOnline()) {
// Try to park the habbo in the grace period instead of immediate disconnect
boolean parked = SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
boolean parked = allowSessionResume && SessionResumeManager.getInstance().parkHabbo(this.habbo, this.ssoTicket);
if (!parked) {
// No grace period configured — immediate disconnect as before
@@ -177,4 +181,4 @@ public class GameClient {
LOGGER.error("Caught exception", e);
}
}
}
}
@@ -43,14 +43,34 @@ public class GameClientManager {
public void disposeClient(GameClient client) {
this.disposeClient(client.getChannel());
if (client == null) {
return;
}
this.disposeClient(client.getChannel(), true);
}
public void forceDisposeClient(GameClient client) {
if (client == null) {
return;
}
this.disposeClient(client.getChannel(), false);
}
private void disposeClient(Channel channel) {
this.disposeClient(channel, true);
}
private void disposeClient(Channel channel, boolean allowSessionResume) {
if (channel == null) {
return;
}
GameClient client = channel.attr(GameServerAttributes.CLIENT).get();
if (client != null) {
client.dispose();
client.dispose(allowSessionResume);
}
channel.deregister();
channel.attr(GameServerAttributes.CLIENT).set(null);
@@ -190,4 +210,4 @@ public class GameClientManager {
CFKeepAlive();
}, 30000);
}
}
}
@@ -111,7 +111,7 @@ public class HabboManager {
habbo = this.cloneCheck(userId);
if (habbo != null) {
habbo.alert(Emulator.getTexts().getValue("loggedin.elsewhere"));
Emulator.getGameServer().getGameClientManager().disposeClient(habbo.getClient());
Emulator.getGameServer().getGameClientManager().forceDisposeClient(habbo.getClient());
habbo = null;
}
@@ -37,10 +37,10 @@ public class MachineIDEvent extends MessageHandler {
// SSO ticket), so Habbo.connect() may have skipped the MAC-ban check for
// lack of a machineId. Enforce it now that the fingerprint is known.
if (Emulator.getGameEnvironment().getModToolManager().hasMACBan(this.client)) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
Emulator.getGameServer().getGameClientManager().forceDisposeClient(this.client);
}
}
LOGGER.debug("Setting client MachineId to {}", storedMachineId);
}
}
}
@@ -306,7 +306,7 @@ public class SecureLoginEvent extends MessageHandler {
Emulator.getPluginManager().fireEvent(userLoginEvent);
if(userLoginEvent.isCancelled()) {
Emulator.getGameServer().getGameClientManager().disposeClient(this.client);
Emulator.getGameServer().getGameClientManager().forceDisposeClient(this.client);
return;
}
@@ -29,7 +29,7 @@ public class DisconnectUser extends RCONMessage<DisconnectUser.DisconnectUserJSO
return;
}
Emulator.getGameServer().getGameClientManager().disposeClient(target.getClient());
Emulator.getGameServer().getGameClientManager().forceDisposeClient(target.getClient());
this.message = Emulator.getTexts().getValue("commands.succes.cmd_disconnect.disconnected").replace("%user%", target.getHabboInfo().getUsername());
}
@@ -40,4 +40,4 @@ public class DisconnectUser extends RCONMessage<DisconnectUser.DisconnectUserJSO
public String username;
}
}
}
@@ -467,7 +467,7 @@ final class AccountChangeEndpoints {
com.eu.habbo.habbohotel.users.Habbo habbo =
Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo != null && habbo.getClient() != null) {
Emulator.getGameServer().getGameClientManager().disposeClient(habbo.getClient());
Emulator.getGameServer().getGameClientManager().forceDisposeClient(habbo.getClient());
}
}
@@ -69,7 +69,7 @@ final class SessionEndpoints {
com.eu.habbo.habbohotel.users.Habbo habbo =
Emulator.getGameServer().getGameClientManager().getHabbo(userId);
if (habbo != null && habbo.getClient() != null) {
Emulator.getGameServer().getGameClientManager().disposeClient(habbo.getClient());
Emulator.getGameServer().getGameClientManager().forceDisposeClient(habbo.getClient());
}
}
}
@@ -0,0 +1,22 @@
package com.eu.habbo.habbohotel.gameclients;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
class GameClientManagerContractTest {
@Test
void exposesExplicitForcedDisposePath() {
assertDoesNotThrow(() -> GameClient.class.getDeclaredMethod("dispose", boolean.class));
assertDoesNotThrow(() -> GameClientManager.class.getDeclaredMethod("forceDisposeClient", GameClient.class));
}
@Test
void disposeMethodsIgnoreNullClient() {
GameClientManager manager = new GameClientManager();
assertDoesNotThrow(() -> manager.disposeClient(null));
assertDoesNotThrow(() -> manager.forceDisposeClient(null));
}
}