Skip to content

Commit a5eac20

Browse files
committed
Add nearly full Eaglercraft server support
TODO: finish making Eaglercraft 1.5 skins show up on Eaglercraft 1.8
1 parent 8a154e8 commit a5eac20

19 files changed

+1389
-56
lines changed

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,5 @@ repositories {
1010
}
1111

1212
dependencies {
13-
implementation files("libs/ViaProxy-3.0.21-SNAPSHOT+java8_PATCHED.jar")
13+
implementation files("libs/ViaProxy-3.0.22-SNAPSHOT+java8.jar")
1414
}

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

Lines changed: 357 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 291 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,291 @@
1+
package me.ayunami2000.ayunViaProxyEagUtils;
2+
3+
import com.google.common.primitives.Ints;
4+
import io.netty.buffer.ByteBuf;
5+
import io.netty.buffer.ByteBufUtil;
6+
import io.netty.buffer.Unpooled;
7+
import io.netty.channel.*;
8+
import io.netty.handler.codec.MessageToMessageCodec;
9+
import io.netty.handler.codec.http.websocketx.*;
10+
import net.raphimc.netminecraft.constants.MCPackets;
11+
import net.raphimc.netminecraft.netty.connection.NetClient;
12+
import net.raphimc.netminecraft.packet.PacketTypes;
13+
import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ClientboundPackets1_5_2;
14+
import net.raphimc.vialegacy.protocols.release.protocol1_6_1to1_5_2.ServerboundPackets1_5_2;
15+
import net.raphimc.vialegacy.protocols.release.protocol1_7_2_5to1_6_4.types.Types1_6_4;
16+
import net.raphimc.vialoader.util.VersionEnum;
17+
import net.raphimc.viaproxy.proxy.session.ProxyConnection;
18+
import net.raphimc.viaproxy.proxy.util.ExceptionUtil;
19+
20+
import java.io.IOException;
21+
import java.nio.charset.StandardCharsets;
22+
import java.util.*;
23+
24+
public class EaglerServerHandler extends MessageToMessageCodec<BinaryWebSocketFrame, ByteBuf> {
25+
private final VersionEnum version;
26+
private final String password;
27+
private final NetClient proxyConnection;
28+
private final Map<UUID, String> uuidStringMap = new HashMap<>();
29+
private final List<UUID> skinsBeingFetched = new ArrayList<>();
30+
private ByteBuf serverBoundPartialPacket = Unpooled.EMPTY_BUFFER;
31+
private ByteBuf clientBoundPartialPacket = Unpooled.EMPTY_BUFFER;
32+
public EaglerServerHandler(NetClient proxyConnection, String password) {
33+
this.version = proxyConnection instanceof ProxyConnection ? ((ProxyConnection) proxyConnection).getServerVersion() : VersionEnum.r1_5_2;
34+
this.password = password;
35+
this.proxyConnection = proxyConnection;
36+
}
37+
38+
@Override
39+
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
40+
ExceptionUtil.handleNettyException(ctx, cause, null);
41+
}
42+
43+
private int handshakeState = 0;
44+
45+
@Override
46+
public void encode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
47+
if (version.isNewerThan(VersionEnum.r1_6_4)) {
48+
if (handshakeState == 0) {
49+
handshakeState = 1;
50+
if (((ProxyConnection) proxyConnection).getGameProfile() == null) {
51+
out.add(Unpooled.EMPTY_BUFFER);
52+
ctx.close();
53+
return;
54+
}
55+
ConnectionHandshake.attemptHandshake(out, ctx.channel(), (ProxyConnection) proxyConnection, password);
56+
if (out.isEmpty()) {
57+
out.add(Unpooled.EMPTY_BUFFER);
58+
}
59+
} else if (handshakeState < 4) {
60+
out.add(Unpooled.EMPTY_BUFFER);
61+
} else {
62+
out.add(new BinaryWebSocketFrame(in.retain()));
63+
}
64+
} else {
65+
ByteBuf bb = ctx.alloc().buffer(serverBoundPartialPacket.readableBytes() + in.readableBytes());
66+
bb.writeBytes(serverBoundPartialPacket);
67+
serverBoundPartialPacket.release();
68+
serverBoundPartialPacket = Unpooled.EMPTY_BUFFER;
69+
bb.writeBytes(in);
70+
int readerIndex = 0;
71+
try {
72+
while (bb.isReadable()) {
73+
readerIndex = bb.readerIndex();
74+
ServerboundPackets1_5_2 pkt = ServerboundPackets1_5_2.getPacket(bb.readUnsignedByte());
75+
pkt.getPacketReader().accept(null, bb);
76+
int len = bb.readerIndex() - readerIndex;
77+
ByteBuf packet = ctx.alloc().buffer(len);
78+
bb.readerIndex(readerIndex);
79+
bb.readBytes(packet, len);
80+
encodeOld(ctx, packet, out);
81+
}
82+
} catch (Exception e) {
83+
bb.readerIndex(readerIndex);
84+
if (bb.readableBytes() > 65535) {
85+
ctx.close();
86+
out.add(Unpooled.EMPTY_BUFFER);
87+
return;
88+
}
89+
serverBoundPartialPacket = ctx.alloc().buffer(bb.readableBytes());
90+
serverBoundPartialPacket.writeBytes(bb);
91+
}
92+
}
93+
if (out.isEmpty()) {
94+
out.add(Unpooled.EMPTY_BUFFER);
95+
}
96+
}
97+
98+
public void encodeOld(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
99+
if (handshakeState == 0) {
100+
handshakeState = 1;
101+
if (in.readableBytes() >= 2 && in.getUnsignedByte(0) == 2) {
102+
in.setByte(1, in.getUnsignedByte(1) + 8);
103+
}
104+
}
105+
if (in.readableBytes() >= 1 && in.getUnsignedByte(0) == 0xFD) {
106+
return;
107+
}
108+
if (in.readableBytes() >= 3 && in.getUnsignedByte(0) == 250) {
109+
in.skipBytes(1);
110+
String tag;
111+
byte[] msg;
112+
try {
113+
tag = Types1_6_4.STRING.read(in);
114+
if (tag.equals("EAG|Skins-1.8")) {
115+
msg = new byte[in.readShort()];
116+
in.readBytes(msg);
117+
if (msg.length == 0) {
118+
throw new IOException("Zero-length packet recieved");
119+
}
120+
final int packetId = msg[0] & 0xFF;
121+
switch (packetId) {
122+
case 3: {
123+
if (msg.length != 17) {
124+
throw new IOException("Invalid length " + msg.length + " for skin request packet");
125+
}
126+
final UUID searchUUID = SkinPackets.bytesToUUID(msg, 1);
127+
if (uuidStringMap.containsKey(searchUUID)) {
128+
// skinsBeingFetched.add(searchUUID);
129+
String name = uuidStringMap.get(searchUUID);
130+
ByteBuf bb = ctx.alloc().buffer();
131+
bb.writeByte((byte) 250);
132+
Types1_6_4.STRING.write(bb, "EAG|FetchSkin"); // todo: get to work
133+
bb.writeByte((byte) 0);
134+
bb.writeByte((byte) 0);
135+
bb.writeBytes(name.getBytes(StandardCharsets.UTF_8));
136+
out.add(new BinaryWebSocketFrame(bb));
137+
}
138+
break;
139+
}
140+
case 6: {
141+
break;
142+
}
143+
default: {
144+
throw new IOException("Unknown packet type " + packetId);
145+
}
146+
}
147+
return;
148+
}
149+
} catch (Exception ignored) {
150+
}
151+
in.resetReaderIndex();
152+
}
153+
out.add(new BinaryWebSocketFrame(in.retain()));
154+
}
155+
156+
@Override
157+
public void decode(ChannelHandlerContext ctx, BinaryWebSocketFrame in, List<Object> out) {
158+
if (version.isNewerThan(VersionEnum.r1_6_4)) {
159+
if (handshakeState == 0) {
160+
out.add(Unpooled.EMPTY_BUFFER);
161+
} else if (handshakeState == 1) {
162+
handshakeState = 2;
163+
ConnectionHandshake.attemptHandshake2(ctx.channel(), ByteBufUtil.getBytes(in.content()), (ProxyConnection) proxyConnection, password);
164+
out.add(Unpooled.EMPTY_BUFFER);
165+
} else if (handshakeState == 2) {
166+
handshakeState = 3;
167+
ConnectionHandshake.attemptHandshake3(ctx.channel(), ByteBufUtil.getBytes(in.content()), (ProxyConnection) proxyConnection);
168+
ByteBuf bb = ctx.alloc().buffer();
169+
PacketTypes.writeVarInt(bb, MCPackets.S2C_LOGIN_SUCCESS.getId(version.getVersion()));
170+
PacketTypes.writeString(bb, ((ProxyConnection) proxyConnection).getGameProfile().getId().toString());
171+
PacketTypes.writeString(bb, ((ProxyConnection) proxyConnection).getGameProfile().getName());
172+
out.add(bb);
173+
} else if (handshakeState == 3) {
174+
handshakeState = 4;
175+
ConnectionHandshake.attemptHandshake4(ctx.channel(), ByteBufUtil.getBytes(in.content()), (ProxyConnection) proxyConnection);
176+
out.add(Unpooled.EMPTY_BUFFER);
177+
} else {
178+
if (in.content().getByte(0) == MCPackets.S2C_LOGIN_SUCCESS.getId(version.getVersion()) && in.content().getByte(1) == 0 && in.content().getByte(2) == 2) {
179+
out.add(Unpooled.EMPTY_BUFFER);
180+
return;
181+
}
182+
if (in.content().getByte(0) == 0) {
183+
in.content().skipBytes(1);
184+
PacketTypes.readVarInt(in.content());
185+
if (in.content().readableBytes() > 0) {
186+
in.content().setByte(0, 0x40);
187+
}
188+
in.content().resetReaderIndex();
189+
}
190+
out.add(in.content().retain());
191+
}
192+
} else {
193+
ByteBuf bb = ctx.alloc().buffer(clientBoundPartialPacket.readableBytes() + in.content().readableBytes());
194+
bb.writeBytes(clientBoundPartialPacket);
195+
clientBoundPartialPacket.release();
196+
clientBoundPartialPacket = Unpooled.EMPTY_BUFFER;
197+
bb.writeBytes(in.content());
198+
int readerIndex = 0;
199+
try {
200+
while (bb.isReadable()) {
201+
readerIndex = bb.readerIndex();
202+
ClientboundPackets1_5_2 pkt = ClientboundPackets1_5_2.getPacket(bb.readUnsignedByte());
203+
pkt.getPacketReader().accept(null, bb);
204+
int len = bb.readerIndex() - readerIndex;
205+
ByteBuf packet = ctx.alloc().buffer(len);
206+
bb.readerIndex(readerIndex);
207+
bb.readBytes(packet, len);
208+
decodeOld(ctx, packet, out);
209+
}
210+
} catch (Exception e) {
211+
bb.readerIndex(readerIndex);
212+
if (bb.readableBytes() > 65535) {
213+
ctx.close();
214+
out.add(Unpooled.EMPTY_BUFFER);
215+
return;
216+
}
217+
clientBoundPartialPacket = ctx.alloc().buffer(bb.readableBytes());
218+
clientBoundPartialPacket.writeBytes(bb);
219+
}
220+
}
221+
if (out.isEmpty()) {
222+
out.add(Unpooled.EMPTY_BUFFER);
223+
}
224+
}
225+
public void decodeOld(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
226+
if (in.getUnsignedByte(0) == 0x14) {
227+
in.skipBytes(5);
228+
try {
229+
String name = Types1_6_4.STRING.read(in);
230+
UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + name).getBytes(StandardCharsets.UTF_8));
231+
uuidStringMap.put(uuid, name);
232+
} catch (Exception ignored) {
233+
}
234+
in.resetReaderIndex();
235+
}
236+
if (in.getUnsignedByte(0) == 0xFD) {
237+
in.writerIndex(0);
238+
in.writeByte((byte) 0xCD);
239+
in.writeByte((byte) 0x00);
240+
ctx.writeAndFlush(new BinaryWebSocketFrame(in.retain()));
241+
return;
242+
}
243+
if (!skinsBeingFetched.isEmpty() && in.readableBytes() >= 3 && in.getUnsignedByte(0) == 250) {
244+
in.skipBytes(1);
245+
String tag;
246+
byte[] msg;
247+
try {
248+
tag = Types1_6_4.STRING.read(in);
249+
// System.out.println(tag);
250+
if (tag.equals("EAG|UserSkin")) {
251+
msg = new byte[in.readShort()];
252+
in.readBytes(msg);
253+
System.out.println(msg.length);
254+
byte[] res = new byte[msg.length - 1];
255+
System.arraycopy(msg, 1, res, 0, res.length);
256+
if (res.length == 8192) {
257+
final int[] tmp1 = new int[2048];
258+
final int[] tmp2 = new int[4096];
259+
for (int i = 0; i < tmp1.length; ++i) {
260+
tmp1[i] = Ints.fromBytes(res[i * 4 + 3], res[i * 4], res[i * 4 + 1], res[i * 4 + 2]);
261+
}
262+
SkinConverter.convert64x32to64x64(tmp1, tmp2);
263+
res = new byte[16384];
264+
for (int i = 0; i < tmp2.length; ++i) {
265+
System.arraycopy(Ints.toByteArray(tmp2[i]), 0, res, i * 4, 4);
266+
}
267+
} else {
268+
for (int j = 0; j < res.length; j += 4) {
269+
final byte tmp3 = res[j + 3];
270+
res[j + 3] = res[j + 2];
271+
res[j + 2] = res[j + 1];
272+
res[j + 1] = res[j];
273+
res[j] = tmp3;
274+
}
275+
}
276+
in.writerIndex(1);
277+
Types1_6_4.STRING.write(in, "EAG|Skins-1.8");
278+
byte[] data = SkinPackets.makeCustomResponse(skinsBeingFetched.remove(0), 0, res);
279+
in.writeShort(data.length);
280+
in.writeBytes(data);
281+
}
282+
} catch (Exception ignored) {
283+
}
284+
in.resetReaderIndex();
285+
}
286+
if (in.getByte(0) == (byte) 0x83 && in.getShort(1) != 358) {
287+
return;
288+
}
289+
out.add(in.retain());
290+
}
291+
}

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,10 @@ public void channelRead(final ChannelHandlerContext ctx, final Object obj) throw
7777
}
7878
try {
7979
if ("EAG|MySkin".equals(tag)) {
80+
if (!FunnyConfig.eaglerSkins) {
81+
bb.release();
82+
return;
83+
}
8084
if (!EaglerSkinHandler.skinCollection.containsKey(uuid)) {
8185
final int t = msg[0] & 0xFF;
8286
if (t < EaglerSkinHandler.SKIN_DATA_SIZE.length && msg.length == EaglerSkinHandler.SKIN_DATA_SIZE[t] + 1) {
@@ -87,6 +91,10 @@ public void channelRead(final ChannelHandlerContext ctx, final Object obj) throw
8791
return;
8892
}
8993
if ("EAG|MyCape".equals(tag)) {
94+
if (!FunnyConfig.eaglerSkins) {
95+
bb.release();
96+
return;
97+
}
9098
if (!EaglerSkinHandler.capeCollection.containsKey(uuid)) {
9199
final int t = msg[0] & 0xFF;
92100
if (t < EaglerSkinHandler.CAPE_DATA_SIZE.length && msg.length == EaglerSkinHandler.CAPE_DATA_SIZE[t] + 2) {
@@ -113,7 +121,7 @@ public void channelRead(final ChannelHandlerContext ctx, final Object obj) throw
113121
conc = conc2;
114122
}
115123
sendData(ctx, "EAG|UserSkin", conc);
116-
} else if (EaglerXSkinHandler.skinService.loadPremiumSkins) {
124+
} else if (FunnyConfig.premiumSkins) {
117125
try {
118126
URL url = new URL("https://playerdb.co/api/player/minecraft/" + fetch);
119127
URLConnection urlConnection = url.openConnection();

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ public void channelRead(final ChannelHandlerContext ctx, final Object obj) throw
7878
super.channelRead(ctx, obj);
7979
return;
8080
}
81+
if (!FunnyConfig.eaglerVoice) {
82+
bb.release();
83+
return;
84+
}
8185
final DataInputStream streamIn = new DataInputStream(new ByteArrayInputStream(msg));
8286
final int sig = streamIn.read();
8387
switch (sig) {

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

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313

1414
public class EaglerXSkinHandler extends ChannelInboundHandlerAdapter {
1515
private final ConcurrentHashMap<String, byte[]> profileData;
16-
public static final SkinService skinService;
16+
public static SkinService skinService;
1717
private String user;
1818
private int pluginMessageId;
1919

@@ -63,15 +63,17 @@ public void channelRead(final ChannelHandlerContext ctx, final Object obj) throw
6363
}
6464
if (this.user == null) {
6565
this.user = ((EaglercraftHandler) ctx.pipeline().get("eaglercraft-handler")).username;
66-
final UUID clientUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8));
67-
if (this.profileData.containsKey("skin_v1")) {
68-
try {
69-
SkinPackets.registerEaglerPlayer(clientUUID, this.profileData.get("skin_v1"), EaglerXSkinHandler.skinService);
70-
} catch (Throwable ex) {
66+
if (FunnyConfig.eaglerSkins) {
67+
final UUID clientUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8));
68+
if (this.profileData.containsKey("skin_v1")) {
69+
try {
70+
SkinPackets.registerEaglerPlayer(clientUUID, this.profileData.get("skin_v1"), EaglerXSkinHandler.skinService);
71+
} catch (Throwable ex) {
72+
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXSkinHandler.skinService);
73+
}
74+
} else {
7175
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXSkinHandler.skinService);
7276
}
73-
} else {
74-
SkinPackets.registerEaglerPlayerFallback(clientUUID, EaglerXSkinHandler.skinService);
7577
}
7678
}
7779
if (this.pluginMessageId <= 0) {
@@ -101,8 +103,4 @@ public void channelInactive(final ChannelHandlerContext ctx) throws Exception {
101103
EaglerXSkinHandler.skinService.unregisterPlayer(UUID.nameUUIDFromBytes(("OfflinePlayer:" + this.user).getBytes(StandardCharsets.UTF_8)));
102104
}
103105
}
104-
105-
static {
106-
skinService = new SkinService();
107-
}
108106
}

0 commit comments

Comments
 (0)