11package me .ayunami2000 .ayunViaProxyEagUtils ;
22
33import 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 ;
48import com .viaversion .viaversion .util .ChatColorUtil ;
59import io .netty .buffer .ByteBuf ;
610import io .netty .buffer .ByteBufUtil ;
711import io .netty .buffer .Unpooled ;
812import io .netty .channel .ChannelHandlerContext ;
913import io .netty .handler .codec .MessageToMessageCodec ;
1014import 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 ;
1118import net .jodah .expiringmap .ExpiringMap ;
1219import net .raphimc .netminecraft .constants .MCPackets ;
1320import net .raphimc .netminecraft .netty .connection .NetClient ;
2027import net .raphimc .viaproxy .proxy .session .ProxyConnection ;
2128import 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 ;
2336import java .io .IOException ;
2437import java .nio .charset .StandardCharsets ;
25- import java .util .HashMap ;
2638import java .util .List ;
27- import java .util .Map ;
28- import java .util .UUID ;
39+ import java .util .*;
2940import 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 );
0 commit comments