From 23cbc975f646b0be6ee81e03ae5c6c458dfac718 Mon Sep 17 00:00:00 2001 From: ayunami2000 Date: Fri, 6 Oct 2023 13:10:28 -0400 Subject: [PATCH] add eaglercraft server status passthru --- .../EaglerServerHandler.java | 224 +++++++++++++++--- .../EaglercraftHandler.java | 5 +- .../EaglercraftInitialHandler.java | 2 - .../ayunViaProxyEagUtils/SkinService.java | 1 - .../WebSocketConnectedNotifier.java | 4 +- 5 files changed, 203 insertions(+), 33 deletions(-) diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java index 921b022..77fb4e7 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java @@ -1,6 +1,10 @@ package me.ayunami2000.ayunViaProxyEagUtils; import com.google.common.primitives.Ints; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.viaversion.viaversion.util.ChatColorUtil; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufUtil; @@ -8,6 +12,9 @@ import io.netty.buffer.Unpooled; 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.TextWebSocketFrame; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; +import io.netty.util.AttributeKey; import net.jodah.expiringmap.ExpiringMap; import net.raphimc.netminecraft.constants.MCPackets; import net.raphimc.netminecraft.netty.connection.NetClient; @@ -20,15 +27,19 @@ import net.raphimc.viaproxy.proxy.session.LegacyProxyConnection; import net.raphimc.viaproxy.proxy.session.ProxyConnection; import net.raphimc.viaproxy.proxy.util.ExceptionUtil; +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.awt.image.DataBufferByte; +import java.awt.image.Raster; +import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.charset.StandardCharsets; -import java.util.HashMap; import java.util.List; -import java.util.Map; -import java.util.UUID; +import java.util.*; import java.util.concurrent.TimeUnit; -public class EaglerServerHandler extends MessageToMessageCodec { +public class EaglerServerHandler extends MessageToMessageCodec { private final VersionEnum version; private final String password; private final NetClient proxyConnection; @@ -53,33 +64,34 @@ public class EaglerServerHandler extends MessageToMessageCodec out) { + if (handshakeState < 0) { + out.add(Unpooled.EMPTY_BUFFER); + return; + } if (version.isNewerThan(VersionEnum.r1_6_4)) { if (in.readableBytes() == 2 && in.getUnsignedByte(0) == 0xFE && in.getUnsignedByte(1) == 0x01) { - // todo: legacy ping - ctx.close(); + handshakeState = -1; + out.add(new TextWebSocketFrame("Accept: MOTD")); return; } - int len = PacketTypes.readVarInt(in); - ByteBuf bb = ctx.alloc().buffer(len); - bb.writeBytes(in); - int id = PacketTypes.readVarInt(bb); - if (id == 0x00) { - PacketTypes.readVarInt(bb); - PacketTypes.readString(bb, 32767); - bb.readUnsignedShort(); - int nextState = PacketTypes.readVarInt(bb); - if (nextState == 1) { - // todo: ping - ctx.close(); - return; - } - } - bb.release(); - in.resetReaderIndex(); - if (handshakeState == 0) { handshakeState = 1; + + int id = PacketTypes.readVarInt(in); + if (id == 0x00) { + PacketTypes.readVarInt(in); + PacketTypes.readString(in, 32767); + in.readUnsignedShort(); + int nextState = PacketTypes.readVarInt(in); + if (nextState == 1) { + handshakeState = -2; + out.add(new TextWebSocketFrame("Accept: MOTD")); + return; + } + } + in.resetReaderIndex(); + if (((ProxyConnection) proxyConnection).getGameProfile() == null) { out.add(Unpooled.EMPTY_BUFFER); ctx.close(); @@ -134,8 +146,8 @@ public class EaglerServerHandler extends MessageToMessageCodec out) { if (in.readableBytes() == 2 && in.getUnsignedByte(0) == 0xFE && in.getUnsignedByte(1) == 0x01) { - // todo: legacy ping - ctx.close(); + handshakeState = -1; + out.add(new TextWebSocketFrame("Accept: MOTD")); return; } if (in.readableBytes() >= 2 && in.getUnsignedByte(0) == 2) { @@ -197,8 +209,166 @@ public class EaglerServerHandler extends MessageToMessageCodec serverInfoKey = AttributeKey.newInstance("server-info"); + public static final AttributeKey eagIconKey = AttributeKey.newInstance("eag-icon"); + private static final AttributeKey eagLegacyStatusKey = AttributeKey.newInstance("eag-legacy-status"); + @Override - public void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame in, List out) { + public void decode(ChannelHandlerContext ctx, WebSocketFrame in, List out) { + if (in instanceof TextWebSocketFrame && handshakeState < 0) { + JsonObject json = JsonParser.parseString(((TextWebSocketFrame) in).text()).getAsJsonObject(); + if (!(json.has("data") && json.get("data").isJsonObject() && (json = json.getAsJsonObject("data")).has("motd") && json.get("motd").isJsonArray() && json.has("icon") && json.get("icon").isJsonPrimitive() && json.has("online") && json.get("online").isJsonPrimitive() && json.has("max") && json.get("max").isJsonPrimitive() && json.has("players") && json.get("players").isJsonArray())) { + out.add(Unpooled.EMPTY_BUFFER); + return; + } + JsonArray motd = json.getAsJsonArray("motd"); + StringBuilder motdSb = new StringBuilder(); + for (JsonElement line : motd) { + motdSb.append(line.getAsString()).append("\n"); + } + if (motdSb.length() > 0) { + motdSb.setLength(motdSb.length() - 1); + } + boolean icon = json.get("icon").getAsBoolean(); + int online = json.get("online").getAsInt(); + int max = json.get("max").getAsInt(); + JsonArray players = json.getAsJsonArray("players"); + if (handshakeState == -1) { + ByteBuf bb = ctx.alloc().buffer(); + bb.writeByte((byte) 0xFF); + StringBuilder sb = new StringBuilder("ยง1\0"); + sb.append(version.getVersion()).append("\0"); + sb.append(version.getName()).append("\0"); + sb.append(motdSb).append("\0"); + sb.append(online).append("\0"); + sb.append(max); + try { + Types1_6_4.STRING.write(bb, sb.toString()); + } catch (Exception ignored) { + } + if (icon) { + ctx.channel().attr(eagLegacyStatusKey).set(bb); + handshakeState = -3; + } else { + out.add(bb); + } + } else if (icon) { + List playerList = new ArrayList<>(); + for (JsonElement player : players) { + playerList.add(player.toString()); + } + ctx.channel().attr(serverInfoKey).set(new ServerInfo(motdSb.toString(), online, max, playerList.toArray(new String[0]))); + } else { + JsonObject resp = new JsonObject(); + JsonObject versionObj = new JsonObject(); + versionObj.addProperty("name", version.getName()); + versionObj.addProperty("protocol", version.getVersion()); + resp.add("version", versionObj); + JsonObject playersObj = new JsonObject(); + playersObj.addProperty("max", max); + playersObj.addProperty("online", online); + if (!players.isEmpty()) { + JsonArray sampleArr = new JsonArray(); + for (JsonElement player : players) { + JsonObject playerObj = new JsonObject(); + playerObj.addProperty("name", player.toString()); + playerObj.addProperty("id", UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(StandardCharsets.UTF_8)).toString()); + sampleArr.add(playerObj); + } + playersObj.add("sample", sampleArr); + } + resp.add("players", playersObj); + JsonObject descriptionObj = new JsonObject(); + descriptionObj.addProperty("text", motdSb.toString()); + resp.add("description", descriptionObj); + ByteBuf bb = ctx.alloc().buffer(); + PacketTypes.writeVarInt(bb, 0); + PacketTypes.writeString(bb, resp.toString()); + out.add(bb); + handshakeState = -1; + } + } + if (!(in instanceof BinaryWebSocketFrame)) { + if (out.isEmpty()) { + out.add(Unpooled.EMPTY_BUFFER); + } + return; + } + if (handshakeState < 0) { + if (handshakeState == -3) { + handshakeState = -1; + if (proxyConnection instanceof ProxyConnection) { + ((ProxyConnection) proxyConnection).getC2P().attr(eagIconKey).set(in.content().retain()); + } else { + ((LegacyProxyConnection) proxyConnection).getC2P().attr(eagIconKey).set(in.content().retain()); + } + out.add(ctx.channel().attr(eagLegacyStatusKey).getAndSet(null)); + return; + } + if (handshakeState == -1) { + out.add(Unpooled.EMPTY_BUFFER); + return; + } + ServerInfo serverInfo = ctx.channel().attr(serverInfoKey).getAndSet(null); + JsonObject resp = new JsonObject(); + JsonObject versionObj = new JsonObject(); + versionObj.addProperty("name", version.getName()); + versionObj.addProperty("protocol", version.getVersion()); + resp.add("version", versionObj); + JsonObject playersObj = new JsonObject(); + playersObj.addProperty("max", serverInfo.max); + playersObj.addProperty("online", serverInfo.online); + if (serverInfo.players.length > 0) { + JsonArray sampleArr = new JsonArray(); + for (String player : serverInfo.players) { + JsonObject playerObj = new JsonObject(); + playerObj.addProperty("name", player); + playerObj.addProperty("id", UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(StandardCharsets.UTF_8)).toString()); + sampleArr.add(playerObj); + } + playersObj.add("sample", sampleArr); + } + resp.add("players", playersObj); + JsonObject descriptionObj = new JsonObject(); + descriptionObj.addProperty("text", serverInfo.motd); + resp.add("description", descriptionObj); + if (in.content().readableBytes() == 16384) { + BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_4BYTE_ABGR); + byte[] pixels = new byte[16384]; + for (int i = 0; i < 4096; i++) { + pixels[i * 4] = in.content().getByte(i * 4 + 3); + pixels[i * 4 + 1] = in.content().getByte(i * 4 + 2); + pixels[i * 4 + 2] = in.content().getByte(i * 4 + 1); + pixels[i * 4 + 3] = in.content().getByte(i * 4); + } + image.setData(Raster.createRaster(image.getSampleModel(), new DataBufferByte(pixels, 16384), new Point())); + ByteArrayOutputStream os = new ByteArrayOutputStream(); + try { + ImageIO.write(image, "png", os); + resp.addProperty("favicon", "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray())); + } catch (IOException ignored) { + } + } + ByteBuf bb = ctx.alloc().buffer(); + PacketTypes.writeVarInt(bb, 0); + PacketTypes.writeString(bb, resp.toString()); + out.add(bb); + handshakeState = -1; + return; + } if (version.isNewerThan(VersionEnum.r1_6_4)) { if (handshakeState == 0) { out.add(Unpooled.EMPTY_BUFFER); diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java index 9bc6007..cd49bf9 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java @@ -97,7 +97,8 @@ public class EaglercraftHandler extends MessageToMessageCodec> 24 & 0xFF); } out.add(new BinaryWebSocketFrame(ctx.alloc().buffer().writeBytes(iconPixels))); + } else if (hasIcon) { + out.add(new BinaryWebSocketFrame(ctx.channel().attr(EaglerServerHandler.eagIconKey).get())); } } else { if (this.state != State.LOGIN_COMPLETE) { diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftInitialHandler.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftInitialHandler.java index 3b410ab..b6fe033 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftInitialHandler.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftInitialHandler.java @@ -4,7 +4,6 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.Channel; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; -import io.netty.channel.ChannelInboundHandler; import io.netty.handler.codec.ByteToMessageDecoder; import io.netty.handler.codec.http.HttpObjectAggregator; import io.netty.handler.codec.http.HttpServerCodec; @@ -12,7 +11,6 @@ import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler; import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; -import net.raphimc.netminecraft.constants.MCPipeline; import net.raphimc.viaproxy.plugins.PluginManager; import net.raphimc.viaproxy.plugins.events.Client2ProxyHandlerCreationEvent; import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer; diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/SkinService.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/SkinService.java index 1fe7fdc..a7d91f5 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/SkinService.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/SkinService.java @@ -6,7 +6,6 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import net.raphimc.netminecraft.constants.MCPackets; import net.raphimc.netminecraft.packet.PacketTypes; -import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4; import javax.imageio.ImageIO; import java.awt.image.DataBufferByte; diff --git a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/WebSocketConnectedNotifier.java b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/WebSocketConnectedNotifier.java index 2c56f88..c218a01 100644 --- a/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/WebSocketConnectedNotifier.java +++ b/src/main/java/me/ayunami2000/ayunViaProxyEagUtils/WebSocketConnectedNotifier.java @@ -4,8 +4,8 @@ import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelDuplexHandler; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelPromise; -import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame; import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler; +import io.netty.handler.codec.http.websocketx.WebSocketFrame; import io.netty.handler.ssl.SslCompletionEvent; import java.util.ArrayList; @@ -52,7 +52,7 @@ public class WebSocketConnectedNotifier extends ChannelDuplexHandler { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { - if (msg instanceof BinaryWebSocketFrame || msg instanceof ByteBuf) { + if (msg instanceof WebSocketFrame || msg instanceof ByteBuf) { msgsWrite.add(new MsgPromise(msg, promise)); } else { ctx.write(msg, promise);