Skip to content

Commit 23cbc97

Browse files
committed
add eaglercraft server status passthru
1 parent 4944715 commit 23cbc97

File tree

5 files changed

+203
-33
lines changed

5 files changed

+203
-33
lines changed

src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglerServerHandler.java

Lines changed: 197 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package me.ayunami2000.ayunViaProxyEagUtils;
22

33
import com.google.common.primitives.Ints;
4+
import com.google.gson.JsonArray;
5+
import com.google.gson.JsonElement;
6+
import com.google.gson.JsonObject;
7+
import com.google.gson.JsonParser;
48
import com.viaversion.viaversion.util.ChatColorUtil;
59
import io.netty.buffer.ByteBuf;
610
import io.netty.buffer.ByteBufUtil;
711
import io.netty.buffer.Unpooled;
812
import io.netty.channel.ChannelHandlerContext;
913
import io.netty.handler.codec.MessageToMessageCodec;
1014
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
15+
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
16+
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
17+
import io.netty.util.AttributeKey;
1118
import net.jodah.expiringmap.ExpiringMap;
1219
import net.raphimc.netminecraft.constants.MCPackets;
1320
import net.raphimc.netminecraft.netty.connection.NetClient;
@@ -20,15 +27,19 @@
2027
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
2128
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
2229

30+
import javax.imageio.ImageIO;
31+
import java.awt.*;
32+
import java.awt.image.BufferedImage;
33+
import java.awt.image.DataBufferByte;
34+
import java.awt.image.Raster;
35+
import java.io.ByteArrayOutputStream;
2336
import java.io.IOException;
2437
import java.nio.charset.StandardCharsets;
25-
import java.util.HashMap;
2638
import java.util.List;
27-
import java.util.Map;
28-
import java.util.UUID;
39+
import java.util.*;
2940
import java.util.concurrent.TimeUnit;
3041

31-
public class EaglerServerHandler extends MessageToMessageCodec<BinaryWebSocketFrame, ByteBuf> {
42+
public class EaglerServerHandler extends MessageToMessageCodec<WebSocketFrame, ByteBuf> {
3243
private final VersionEnum version;
3344
private final String password;
3445
private final NetClient proxyConnection;
@@ -53,33 +64,34 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
5364

5465
@Override
5566
public void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
67+
if (handshakeState < 0) {
68+
out.add(Unpooled.EMPTY_BUFFER);
69+
return;
70+
}
5671
if (version.isNewerThan(VersionEnum.r1_6_4)) {
5772
if (in.readableBytes() == 2 && in.getUnsignedByte(0) == 0xFE && in.getUnsignedByte(1) == 0x01) {
58-
// todo: legacy ping
59-
ctx.close();
73+
handshakeState = -1;
74+
out.add(new TextWebSocketFrame("Accept: MOTD"));
6075
return;
6176
}
6277

63-
int len = PacketTypes.readVarInt(in);
64-
ByteBuf bb = ctx.alloc().buffer(len);
65-
bb.writeBytes(in);
66-
int id = PacketTypes.readVarInt(bb);
67-
if (id == 0x00) {
68-
PacketTypes.readVarInt(bb);
69-
PacketTypes.readString(bb, 32767);
70-
bb.readUnsignedShort();
71-
int nextState = PacketTypes.readVarInt(bb);
72-
if (nextState == 1) {
73-
// todo: ping
74-
ctx.close();
75-
return;
76-
}
77-
}
78-
bb.release();
79-
in.resetReaderIndex();
80-
8178
if (handshakeState == 0) {
8279
handshakeState = 1;
80+
81+
int id = PacketTypes.readVarInt(in);
82+
if (id == 0x00) {
83+
PacketTypes.readVarInt(in);
84+
PacketTypes.readString(in, 32767);
85+
in.readUnsignedShort();
86+
int nextState = PacketTypes.readVarInt(in);
87+
if (nextState == 1) {
88+
handshakeState = -2;
89+
out.add(new TextWebSocketFrame("Accept: MOTD"));
90+
return;
91+
}
92+
}
93+
in.resetReaderIndex();
94+
8395
if (((ProxyConnection) proxyConnection).getGameProfile() == null) {
8496
out.add(Unpooled.EMPTY_BUFFER);
8597
ctx.close();
@@ -134,8 +146,8 @@ public void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
134146

135147
public void encodeOld(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
136148
if (in.readableBytes() == 2 && in.getUnsignedByte(0) == 0xFE && in.getUnsignedByte(1) == 0x01) {
137-
// todo: legacy ping
138-
ctx.close();
149+
handshakeState = -1;
150+
out.add(new TextWebSocketFrame("Accept: MOTD"));
139151
return;
140152
}
141153
if (in.readableBytes() >= 2 && in.getUnsignedByte(0) == 2) {
@@ -197,8 +209,166 @@ public void encodeOld(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
197209
out.add(new BinaryWebSocketFrame(in.retain()));
198210
}
199211

212+
private static class ServerInfo {
213+
public final String motd;
214+
public final int online;
215+
public final int max;
216+
public final String[] players;
217+
public ServerInfo(String motd, int online, int max, String[] players) {
218+
this.motd = motd;
219+
this.online = online;
220+
this.max = max;
221+
this.players = players;
222+
}
223+
}
224+
225+
private static final AttributeKey<ServerInfo> serverInfoKey = AttributeKey.newInstance("server-info");
226+
public static final AttributeKey<ByteBuf> eagIconKey = AttributeKey.newInstance("eag-icon");
227+
private static final AttributeKey<ByteBuf> eagLegacyStatusKey = AttributeKey.newInstance("eag-legacy-status");
228+
200229
@Override
201-
public void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame in, List<Object> out) {
230+
public void decode(ChannelHandlerContext ctx, WebSocketFrame in, List<Object> out) {
231+
if (in instanceof TextWebSocketFrame && handshakeState < 0) {
232+
JsonObject json = JsonParser.parseString(((TextWebSocketFrame) in).text()).getAsJsonObject();
233+
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())) {
234+
out.add(Unpooled.EMPTY_BUFFER);
235+
return;
236+
}
237+
JsonArray motd = json.getAsJsonArray("motd");
238+
StringBuilder motdSb = new StringBuilder();
239+
for (JsonElement line : motd) {
240+
motdSb.append(line.getAsString()).append("\n");
241+
}
242+
if (motdSb.length() > 0) {
243+
motdSb.setLength(motdSb.length() - 1);
244+
}
245+
boolean icon = json.get("icon").getAsBoolean();
246+
int online = json.get("online").getAsInt();
247+
int max = json.get("max").getAsInt();
248+
JsonArray players = json.getAsJsonArray("players");
249+
if (handshakeState == -1) {
250+
ByteBuf bb = ctx.alloc().buffer();
251+
bb.writeByte((byte) 0xFF);
252+
StringBuilder sb = new StringBuilder("§1\0");
253+
sb.append(version.getVersion()).append("\0");
254+
sb.append(version.getName()).append("\0");
255+
sb.append(motdSb).append("\0");
256+
sb.append(online).append("\0");
257+
sb.append(max);
258+
try {
259+
Types1_6_4.STRING.write(bb, sb.toString());
260+
} catch (Exception ignored) {
261+
}
262+
if (icon) {
263+
ctx.channel().attr(eagLegacyStatusKey).set(bb);
264+
handshakeState = -3;
265+
} else {
266+
out.add(bb);
267+
}
268+
} else if (icon) {
269+
List<String> playerList = new ArrayList<>();
270+
for (JsonElement player : players) {
271+
playerList.add(player.toString());
272+
}
273+
ctx.channel().attr(serverInfoKey).set(new ServerInfo(motdSb.toString(), online, max, playerList.toArray(new String[0])));
274+
} else {
275+
JsonObject resp = new JsonObject();
276+
JsonObject versionObj = new JsonObject();
277+
versionObj.addProperty("name", version.getName());
278+
versionObj.addProperty("protocol", version.getVersion());
279+
resp.add("version", versionObj);
280+
JsonObject playersObj = new JsonObject();
281+
playersObj.addProperty("max", max);
282+
playersObj.addProperty("online", online);
283+
if (!players.isEmpty()) {
284+
JsonArray sampleArr = new JsonArray();
285+
for (JsonElement player : players) {
286+
JsonObject playerObj = new JsonObject();
287+
playerObj.addProperty("name", player.toString());
288+
playerObj.addProperty("id", UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(StandardCharsets.UTF_8)).toString());
289+
sampleArr.add(playerObj);
290+
}
291+
playersObj.add("sample", sampleArr);
292+
}
293+
resp.add("players", playersObj);
294+
JsonObject descriptionObj = new JsonObject();
295+
descriptionObj.addProperty("text", motdSb.toString());
296+
resp.add("description", descriptionObj);
297+
ByteBuf bb = ctx.alloc().buffer();
298+
PacketTypes.writeVarInt(bb, 0);
299+
PacketTypes.writeString(bb, resp.toString());
300+
out.add(bb);
301+
handshakeState = -1;
302+
}
303+
}
304+
if (!(in instanceof BinaryWebSocketFrame)) {
305+
if (out.isEmpty()) {
306+
out.add(Unpooled.EMPTY_BUFFER);
307+
}
308+
return;
309+
}
310+
if (handshakeState < 0) {
311+
if (handshakeState == -3) {
312+
handshakeState = -1;
313+
if (proxyConnection instanceof ProxyConnection) {
314+
((ProxyConnection) proxyConnection).getC2P().attr(eagIconKey).set(in.content().retain());
315+
} else {
316+
((LegacyProxyConnection) proxyConnection).getC2P().attr(eagIconKey).set(in.content().retain());
317+
}
318+
out.add(ctx.channel().attr(eagLegacyStatusKey).getAndSet(null));
319+
return;
320+
}
321+
if (handshakeState == -1) {
322+
out.add(Unpooled.EMPTY_BUFFER);
323+
return;
324+
}
325+
ServerInfo serverInfo = ctx.channel().attr(serverInfoKey).getAndSet(null);
326+
JsonObject resp = new JsonObject();
327+
JsonObject versionObj = new JsonObject();
328+
versionObj.addProperty("name", version.getName());
329+
versionObj.addProperty("protocol", version.getVersion());
330+
resp.add("version", versionObj);
331+
JsonObject playersObj = new JsonObject();
332+
playersObj.addProperty("max", serverInfo.max);
333+
playersObj.addProperty("online", serverInfo.online);
334+
if (serverInfo.players.length > 0) {
335+
JsonArray sampleArr = new JsonArray();
336+
for (String player : serverInfo.players) {
337+
JsonObject playerObj = new JsonObject();
338+
playerObj.addProperty("name", player);
339+
playerObj.addProperty("id", UUID.nameUUIDFromBytes(("OfflinePlayer:" + player).getBytes(StandardCharsets.UTF_8)).toString());
340+
sampleArr.add(playerObj);
341+
}
342+
playersObj.add("sample", sampleArr);
343+
}
344+
resp.add("players", playersObj);
345+
JsonObject descriptionObj = new JsonObject();
346+
descriptionObj.addProperty("text", serverInfo.motd);
347+
resp.add("description", descriptionObj);
348+
if (in.content().readableBytes() == 16384) {
349+
BufferedImage image = new BufferedImage(64, 64, BufferedImage.TYPE_4BYTE_ABGR);
350+
byte[] pixels = new byte[16384];
351+
for (int i = 0; i < 4096; i++) {
352+
pixels[i * 4] = in.content().getByte(i * 4 + 3);
353+
pixels[i * 4 + 1] = in.content().getByte(i * 4 + 2);
354+
pixels[i * 4 + 2] = in.content().getByte(i * 4 + 1);
355+
pixels[i * 4 + 3] = in.content().getByte(i * 4);
356+
}
357+
image.setData(Raster.createRaster(image.getSampleModel(), new DataBufferByte(pixels, 16384), new Point()));
358+
ByteArrayOutputStream os = new ByteArrayOutputStream();
359+
try {
360+
ImageIO.write(image, "png", os);
361+
resp.addProperty("favicon", "data:image/png;base64," + Base64.getEncoder().encodeToString(os.toByteArray()));
362+
} catch (IOException ignored) {
363+
}
364+
}
365+
ByteBuf bb = ctx.alloc().buffer();
366+
PacketTypes.writeVarInt(bb, 0);
367+
PacketTypes.writeString(bb, resp.toString());
368+
out.add(bb);
369+
handshakeState = -1;
370+
return;
371+
}
202372
if (version.isNewerThan(VersionEnum.r1_6_4)) {
203373
if (handshakeState == 0) {
204374
out.add(Unpooled.EMPTY_BUFFER);

src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftHandler.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -97,7 +97,8 @@ protected void encode(final ChannelHandlerContext ctx, final ByteBuf in, final L
9797
}
9898
}
9999
data.add("motd", motd);
100-
data.addProperty("icon", root.has("favicon"));
100+
boolean hasIcon = root.has("favicon") || ctx.channel().hasAttr(EaglerServerHandler.eagIconKey);
101+
data.addProperty("icon", hasIcon);
101102
if (root.has("players")) {
102103
final JsonObject javaPlayers = root.getAsJsonObject("players");
103104
data.add("online", javaPlayers.get("online"));
@@ -121,6 +122,8 @@ protected void encode(final ChannelHandlerContext ctx, final ByteBuf in, final L
121122
iconPixels[i * 4 + 3] = (byte) (pixels[i] >> 24 & 0xFF);
122123
}
123124
out.add(new BinaryWebSocketFrame(ctx.alloc().buffer().writeBytes(iconPixels)));
125+
} else if (hasIcon) {
126+
out.add(new BinaryWebSocketFrame(ctx.channel().attr(EaglerServerHandler.eagIconKey).get()));
124127
}
125128
} else {
126129
if (this.state != State.LOGIN_COMPLETE) {

src/main/java/me/ayunami2000/ayunViaProxyEagUtils/EaglercraftInitialHandler.java

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,13 @@
44
import io.netty.channel.Channel;
55
import io.netty.channel.ChannelHandler;
66
import io.netty.channel.ChannelHandlerContext;
7-
import io.netty.channel.ChannelInboundHandler;
87
import io.netty.handler.codec.ByteToMessageDecoder;
98
import io.netty.handler.codec.http.HttpObjectAggregator;
109
import io.netty.handler.codec.http.HttpServerCodec;
1110
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
1211
import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;
1312
import io.netty.handler.ssl.SslContext;
1413
import io.netty.handler.ssl.SslContextBuilder;
15-
import net.raphimc.netminecraft.constants.MCPipeline;
1614
import net.raphimc.viaproxy.plugins.PluginManager;
1715
import net.raphimc.viaproxy.plugins.events.Client2ProxyHandlerCreationEvent;
1816
import net.raphimc.viaproxy.proxy.client2proxy.Client2ProxyChannelInitializer;

src/main/java/me/ayunami2000/ayunViaProxyEagUtils/SkinService.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@
66
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
77
import net.raphimc.netminecraft.constants.MCPackets;
88
import net.raphimc.netminecraft.packet.PacketTypes;
9-
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
109

1110
import javax.imageio.ImageIO;
1211
import java.awt.image.DataBufferByte;

src/main/java/me/ayunami2000/ayunViaProxyEagUtils/WebSocketConnectedNotifier.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import io.netty.channel.ChannelDuplexHandler;
55
import io.netty.channel.ChannelHandlerContext;
66
import io.netty.channel.ChannelPromise;
7-
import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;
87
import io.netty.handler.codec.http.websocketx.WebSocketClientProtocolHandler;
8+
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
99
import io.netty.handler.ssl.SslCompletionEvent;
1010

1111
import java.util.ArrayList;
@@ -52,7 +52,7 @@ public void channelRead(final ChannelHandlerContext ctx, final Object msg) throw
5252

5353
@Override
5454
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
55-
if (msg instanceof BinaryWebSocketFrame || msg instanceof ByteBuf) {
55+
if (msg instanceof WebSocketFrame || msg instanceof ByteBuf) {
5656
msgsWrite.add(new MsgPromise(msg, promise));
5757
} else {
5858
ctx.write(msg, promise);

0 commit comments

Comments
 (0)