🆙 No more Websocket plugin required

- Remove the websocket plugin, and edit the config.ini as in the example
This commit is contained in:
duckietm
2026-02-06 10:49:31 +01:00
parent 400cbab58c
commit 4610e40cd5
9 changed files with 310 additions and 7 deletions
@@ -14,6 +14,7 @@ import com.eu.habbo.messages.outgoing.rooms.FloodCounterComposer;
import com.eu.habbo.messages.outgoing.rooms.ForwardToRoomComposer;
import com.eu.habbo.messages.outgoing.rooms.users.*;
import com.eu.habbo.messages.outgoing.users.*;
import com.eu.habbo.networking.gameserver.GameServerAttributes;
import com.eu.habbo.plugin.events.users.UserCreditsEvent;
import com.eu.habbo.plugin.events.users.UserDisconnectEvent;
import com.eu.habbo.plugin.events.users.UserGetIPAddressEvent;
@@ -118,15 +119,20 @@ public class Habbo implements Runnable {
public boolean connect() {
String ip = "";
String ProxyIP = "";
String proxyInfo = "";
if (!Emulator.getConfig().getBoolean("networking.tcp.proxy") && this.client.getChannel().remoteAddress() != null) {
String wsIp = this.client.getChannel().attr(GameServerAttributes.WS_IP).get();
if (wsIp != null && !wsIp.isEmpty()) {
ip = wsIp;
SocketAddress address = this.client.getChannel().remoteAddress();
proxyInfo = ((InetSocketAddress) address).getAddress().getHostAddress();
} else if (!Emulator.getConfig().getBoolean("networking.tcp.proxy") && this.client.getChannel().remoteAddress() != null) {
SocketAddress address = this.client.getChannel().remoteAddress();
ip = ((InetSocketAddress) address).getAddress().getHostAddress();
ProxyIP = "- no proxy server used";
proxyInfo = "- no proxy server used";
} else {
SocketAddress address = this.client.getChannel().remoteAddress();
ProxyIP = ((InetSocketAddress) address).getAddress().getHostAddress();
proxyInfo = ((InetSocketAddress) address).getAddress().getHostAddress();
}
if (Emulator.getPluginManager().isRegistered(UserGetIPAddressEvent.class, true)) {
@@ -170,7 +176,7 @@ public class Habbo implements Runnable {
this.messenger.connectionChanged(this, true, false);
Emulator.getGameEnvironment().getRoomManager().loadRoomsForHabbo(this);
LOGGER.info("{} logged in from IP {} using proxyserver {}", this.habboInfo.getUsername(), this.habboInfo.getIpLogin(), ProxyIP);
LOGGER.info("{} logged in from IP {} using proxyserver {}", this.habboInfo.getUsername(), this.habboInfo.getIpLogin(), proxyInfo);
LOGGER.info("{} client MachineId = {}", this.habboInfo.getUsername(), this.client.getMachineId());
return true;
}
@@ -13,7 +13,7 @@ public class RequestRoomSettingsEvent extends MessageHandler {
Room room = Emulator.getGameEnvironment().getRoomManager().getRoom(roomId);
if (room == null) return;
if (!room.hasRights(this.client.getHabbo()) || !this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) return;
if (!room.hasRights(this.client.getHabbo()) && !this.client.getHabbo().hasPermission(Permission.ACC_ANYROOMOWNER)) return;
this.client.sendResponse(new RoomSettingsComposer(room));
}
@@ -8,13 +8,24 @@ import com.eu.habbo.networking.gameserver.decoders.*;
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageEncoder;
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageLogger;
import com.eu.habbo.networking.gameserver.handlers.IdleTimeoutHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.UnpooledByteBufAllocator;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.FixedRecvByteBufAllocator;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.logging.LoggingHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class GameServer extends Server {
private static final Logger LOGGER = LoggerFactory.getLogger(GameServer.class);
private final PacketManager packetManager;
private final GameClientManager gameClientManager;
private ServerBootstrap webSocketBootstrap;
public GameServer(String host, int port) throws Exception {
super("Game Server", host, port, Emulator.getConfig().getInt("io.bossgroup.threads"), Emulator.getConfig().getInt("io.workergroup.threads"));
@@ -51,6 +62,41 @@ public class GameServer extends Server {
}
}
});
initializeWebSocketServer();
}
private void initializeWebSocketServer() {
if (!Emulator.getConfig().getBoolean("ws.enabled", false)) {
return;
}
String wsHost = Emulator.getConfig().getValue("ws.host", "0.0.0.0");
int wsPort = Emulator.getConfig().getInt("ws.port", 2096);
WebSocketChannelInitializer wsInitializer = new WebSocketChannelInitializer();
this.webSocketBootstrap = new ServerBootstrap();
this.webSocketBootstrap.group(this.getBossGroup(), this.getWorkerGroup());
this.webSocketBootstrap.channel(NioServerSocketChannel.class);
this.webSocketBootstrap.childOption(ChannelOption.TCP_NODELAY, true);
this.webSocketBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
this.webSocketBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
this.webSocketBootstrap.childOption(ChannelOption.SO_RCVBUF, 4096);
this.webSocketBootstrap.childOption(ChannelOption.RCVBUF_ALLOCATOR, new FixedRecvByteBufAllocator(4096));
this.webSocketBootstrap.childOption(ChannelOption.ALLOCATOR, new UnpooledByteBufAllocator(false));
this.webSocketBootstrap.childHandler(wsInitializer);
ChannelFuture wsFuture = this.webSocketBootstrap.bind(wsHost, wsPort);
while (!wsFuture.isDone()) {
}
if (!wsFuture.isSuccess()) {
LOGGER.error("Failed to start WebSocket server on {}:{}", wsHost, wsPort);
} else {
LOGGER.info("WebSocket server started on {}:{} (SSL: {})", wsHost, wsPort, wsInitializer.isSslEnabled());
}
}
public PacketManager getPacketManager() {
@@ -9,5 +9,5 @@ public class GameServerAttributes {
public static final AttributeKey<GameClient> CLIENT = AttributeKey.valueOf("GameClient");
public static final AttributeKey<HabboRC4> CRYPTO_CLIENT = AttributeKey.valueOf("CryptoClient");
public static final AttributeKey<HabboRC4> CRYPTO_SERVER = AttributeKey.valueOf("CryptoServer");
public static final AttributeKey<String> WS_IP = AttributeKey.valueOf("WebSocketIP");
}
@@ -0,0 +1,79 @@
package com.eu.habbo.networking.gameserver;
import com.eu.habbo.messages.PacketManager;
import com.eu.habbo.networking.gameserver.codec.WebSocketCodec;
import com.eu.habbo.networking.gameserver.decoders.*;
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageEncoder;
import com.eu.habbo.networking.gameserver.encoders.GameServerMessageLogger;
import com.eu.habbo.networking.gameserver.handlers.IdleTimeoutHandler;
import com.eu.habbo.networking.gameserver.handlers.WebSocketHttpHandler;
import com.eu.habbo.networking.gameserver.ssl.SSLCertificateLoader;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolConfig;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslHandler;
import javax.net.ssl.SSLEngine;
public class WebSocketChannelInitializer extends ChannelInitializer<SocketChannel> {
private static final int MAX_FRAME_SIZE = 500000;
private final SslContext sslContext;
private final boolean sslEnabled;
private final WebSocketServerProtocolConfig wsConfig;
public WebSocketChannelInitializer() {
this.sslContext = SSLCertificateLoader.getContext();
this.sslEnabled = this.sslContext != null;
this.wsConfig = WebSocketServerProtocolConfig.newBuilder()
.websocketPath("/")
.checkStartsWith(true)
.maxFramePayloadLength(MAX_FRAME_SIZE)
.build();
}
@Override
protected void initChannel(SocketChannel ch) {
ch.pipeline().addLast("logger", new LoggingHandler());
if (this.sslEnabled) {
SSLEngine engine = this.sslContext.newEngine(ch.alloc());
ch.pipeline().addLast(new SslHandler(engine));
}
ch.pipeline().addLast("httpCodec", new HttpServerCodec());
ch.pipeline().addLast("httpAggregator", new HttpObjectAggregator(MAX_FRAME_SIZE));
ch.pipeline().addLast("wsHttpHandler", new WebSocketHttpHandler());
ch.pipeline().addLast("wsProtocolHandler", new WebSocketServerProtocolHandler(this.wsConfig));
ch.pipeline().addLast("wsCodec", new WebSocketCodec());
// Standard game decoders
ch.pipeline().addLast(new GamePolicyDecoder());
ch.pipeline().addLast(new GameByteFrameDecoder());
ch.pipeline().addLast(new GameByteDecoder());
if (PacketManager.DEBUG_SHOW_PACKETS) {
ch.pipeline().addLast(new GameClientMessageLogger());
}
ch.pipeline().addLast("idleEventHandler", new IdleTimeoutHandler(30, 60));
ch.pipeline().addLast(new GameMessageRateLimit());
ch.pipeline().addLast(new GameMessageHandler());
// Encoders
ch.pipeline().addLast("messageEncoder", new GameServerMessageEncoder());
if (PacketManager.DEBUG_SHOW_PACKETS) {
ch.pipeline().addLast(new GameServerMessageLogger());
}
}
public boolean isSslEnabled() {
return this.sslEnabled;
}
}
@@ -0,0 +1,21 @@
package com.eu.habbo.networking.gameserver.codec;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.MessageToMessageCodec;
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import java.util.List;
public class WebSocketCodec extends MessageToMessageCodec<WebSocketFrame, ByteBuf> {
@Override
protected void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
out.add(new BinaryWebSocketFrame(in).retain());
}
@Override
protected void decode(ChannelHandlerContext ctx, WebSocketFrame in, List<Object> out) {
out.add(in.content().retain());
}
}
@@ -0,0 +1,84 @@
package com.eu.habbo.networking.gameserver.handlers;
import com.eu.habbo.Emulator;
import com.eu.habbo.networking.gameserver.GameServerAttributes;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.*;
import io.netty.util.ReferenceCountUtil;
import java.net.URI;
public class WebSocketHttpHandler extends ChannelInboundHandlerAdapter {
private static final String ORIGIN_HEADER = "Origin";
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof HttpMessage) {
if (!handleHttpRequest(ctx, (HttpMessage) msg)) {
ReferenceCountUtil.release(msg);
return;
}
}
super.channelRead(ctx, msg);
ctx.pipeline().remove(this);
}
private boolean handleHttpRequest(ChannelHandlerContext ctx, HttpMessage req) {
String origin = "error";
try {
if (req.headers().contains(ORIGIN_HEADER)) {
origin = getDomainNameFromUrl(req.headers().get(ORIGIN_HEADER));
}
} catch (Exception ignored) {
}
String whitelist = Emulator.getConfig().getValue("ws.whitelist", "localhost");
if (!isWhitelisted(origin, whitelist.split(","))) {
FullHttpResponse response = new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1,
HttpResponseStatus.FORBIDDEN,
Unpooled.wrappedBuffer("Origin forbidden".getBytes())
);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
return false;
}
String ipHeader = Emulator.getConfig().getValue("ws.ip.header", "");
if (!ipHeader.isEmpty() && req.headers().contains(ipHeader)) {
String ip = req.headers().get(ipHeader);
ctx.channel().attr(GameServerAttributes.WS_IP).set(ip);
}
return true;
}
private static String getDomainNameFromUrl(String url) throws Exception {
URI uri = new URI(url);
String domain = uri.getHost();
return domain.startsWith("www.") ? domain.substring(4) : domain;
}
private static boolean isWhitelisted(String toCheck, String[] whitelist) {
for (String entry : whitelist) {
String trimmed = entry.trim();
if (trimmed.equals("*")) {
return true;
}
if (trimmed.startsWith("*")) {
String suffix = trimmed.substring(1);
if (toCheck.endsWith(suffix) || ("." + toCheck).equals(suffix)) {
return true;
}
} else {
if (toCheck.equals(trimmed)) {
return true;
}
}
}
return false;
}
}
@@ -0,0 +1,32 @@
package com.eu.habbo.networking.gameserver.ssl;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
public class SSLCertificateLoader {
private static final String SSL_PATH = "ssl";
private static final Logger LOGGER = LoggerFactory.getLogger(SSLCertificateLoader.class);
public static SslContext getContext() {
try {
File certFile = new File(SSL_PATH + File.separator + "cert.pem");
File keyFile = new File(SSL_PATH + File.separator + "privkey.pem");
if (!certFile.exists() || !keyFile.exists()) {
LOGGER.debug("SSL certificates not found in '{}' directory, WSS disabled", SSL_PATH);
return null;
}
SslContext context = SslContextBuilder.forServer(certFile, keyFile).build();
LOGGER.info("SSL certificates loaded successfully, WSS enabled");
return context;
} catch (Exception e) {
LOGGER.warn("Failed to load SSL certificates: {}", e.getMessage());
return null;
}
}
}
@@ -0,0 +1,35 @@
#Database Configuration.
db.hostname=localhost
db.port=3306
db.database=ms
db.username=root
db.password=root
db.params=
db.pool.minsize=25
db.pool.maxsize=100
#Game Configuration.
#Host IP. Most likely just 0.0.0.0 Use 127.0.0.1 if you want to play on LAN.
game.host=0.0.0.0
game.port=3000
#RCON Configuration.
#RCON Host IP. Leave this at 127.0.0.1 if you're running your website on the same server as the emulator.
rcon.host=127.0.0.1
rcon.port=3001
rcon.allowed=127.0.0.1;127.0.0.2
#WebSocket Configuration (for Nitro)
#Set ws.enabled to true to enable WebSocket connections.
ws.enabled=false
ws.host=0.0.0.0
ws.port=2096
#Comma-separated whitelist of allowed origins. Supports wildcards: *.example.com, * (allow all)
ws.whitelist=localhost
#Header name for real client IP when behind a proxy (e.g., X-Forwarded-For, CF-Connecting-IP). Leave empty if not using a proxy.
ws.ip.header=
enc.enabled=false
enc.e=3
enc.n=86851dd364d5c5cece3c883171cc6ddc5760779b992482bd1e20dd296888df91b33b936a7b93f06d29e8870f703a216257dec7c81de0058fea4cc5116f75e6efc4e9113513e45357dc3fd43d4efab5963ef178b78bd61e81a14c603b24c8bcce0a12230b320045498edc29282ff0603bc7b7dae8fc1b05b52b2f301a9dc783b7
enc.d=59ae13e243392e89ded305764bdd9e92e4eafa67bb6dac7e1415e8c645b0950bccd26246fd0d4af37145af5fa026c0ec3a94853013eaae5ff1888360f4f9449ee023762ec195dff3f30ca0b08b8c947e3859877b5d7dced5c8715c58b53740b84e11fbc71349a27c31745fcefeeea57cff291099205e230e0c7c27e8e1c0512b