Update #51 - Protocol and FPS improvements, better workspace

This commit is contained in:
lax1dude
2025-05-18 15:01:06 -07:00
parent 71c61e33fd
commit 325a6826bf
1191 changed files with 9266 additions and 187695 deletions

View File

@ -1,507 +0,0 @@
/*
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.ClientUUIDLoadingCache;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EagUtils;
import net.lax1dude.eaglercraft.v1_8.EaglerInputStream;
import net.lax1dude.eaglercraft.v1_8.EaglerOutputStream;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.PauseMenuCustomizeState;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA256Digest;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.GuiAuthenticationScreen;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.GuiConnecting;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.EnumChatFormatting;
import net.minecraft.util.IChatComponent;
public class ConnectionHandshake {
private static final long baseTimeout = 10000l;
private static final int protocolV2 = 2;
private static final int protocolV3 = 3;
private static final int protocolV4 = 4;
private static final Logger logger = LogManager.getLogger();
public static String pluginVersion = null;
public static String pluginBrand = null;
public static int protocolVersion = -1;
public static byte[] getSPHandshakeProtocolData() {
try {
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeShort(3); // supported eagler protocols count
d.writeShort(protocolV2); // client supports v2
d.writeShort(protocolV3); // client supports v3
d.writeShort(protocolV4); // client supports v4
return bao.toByteArray();
}catch(IOException ex) {
throw new RuntimeException(ex);
}
}
public static boolean attemptHandshake(Minecraft mc, IWebSocketClient client, GuiConnecting connecting,
GuiScreen ret, String password, boolean allowPlaintext, boolean enableCookies, byte[] cookieData) {
try {
EaglerProfile.clearServerSkinOverride();
PauseMenuCustomizeState.reset();
ClientUUIDLoadingCache.resetFlags();
pluginVersion = null;
pluginBrand = null;
protocolVersion = -1;
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_VERSION);
d.writeByte(2); // legacy protocol version
d.write(getSPHandshakeProtocolData()); // write supported eagler protocol versions
d.writeShort(1); // supported game protocols count
d.writeShort(47); // client supports 1.8 protocol
String clientBrand = EaglercraftVersion.projectForkName;
d.writeByte(clientBrand.length());
d.writeBytes(clientBrand);
String clientVers = EaglercraftVersion.projectOriginVersion;
d.writeByte(clientVers.length());
d.writeBytes(clientVers);
d.writeBoolean(password != null);
String username = mc.getSession().getProfile().getName();
d.writeByte(username.length());
d.writeBytes(username);
client.send(bao.toByteArray());
byte[] read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for server protocol response!");
return false;
}
DataInputStream di = new DataInputStream(new EaglerInputStream(read));
int type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH) {
StringBuilder protocols = new StringBuilder();
int c = di.readShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
protocols.append(", ");
}
protocols.append("v").append(di.readShort());
}
StringBuilder games = new StringBuilder();
c = di.readShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
games.append(", ");
}
games.append("mc").append(di.readShort());
}
logger.info("Incompatible client: v2/v3/v4 & mc47");
logger.info("Server supports: {}", protocols);
logger.info("Server supports: {}", games);
int msgLen = di.read();
byte[] dat = new byte[msgLen];
di.read(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText(msg)));
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_VERSION) {
protocolVersion = di.readShort();
if(protocolVersion != protocolV2 && protocolVersion != protocolV3 && protocolVersion != protocolV4) {
logger.info("Incompatible server version: {}", protocolVersion);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText(protocolVersion < protocolV2 ? "Outdated Server" : "Outdated Client")));
return false;
}
int gameVers = di.readShort();
if(gameVers != 47) {
logger.info("Incompatible minecraft protocol version: {}", gameVers);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText("This server does not support 1.8!")));
return false;
}
logger.info("Server protocol: {}", protocolVersion);
int msgLen = di.read();
byte[] dat = new byte[msgLen];
di.read(dat);
pluginBrand = ArrayUtils.asciiString(dat);
msgLen = di.read();
dat = new byte[msgLen];
di.read(dat);
pluginVersion = ArrayUtils.asciiString(dat);
logger.info("Server version: {}", pluginVersion);
logger.info("Server brand: {}", pluginBrand);
int authType = di.read();
int saltLength = (int)di.readShort() & 0xFFFF;
byte[] salt = new byte[saltLength];
di.read(salt);
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
d.writeByte(username.length());
d.writeBytes(username);
String requestedServer = "default";
d.writeByte(requestedServer.length());
d.writeBytes(requestedServer);
if(authType != 0 && password != null && password.length() > 0) {
if(authType == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
if(allowPlaintext) {
logger.warn("Server is using insecure plaintext authentication");
d.writeByte(password.length() << 1);
d.writeChars(password);
}else {
logger.error("Plaintext authentication was attempted but no user confirmation has been given to proceed");
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed",
new ChatComponentText(EnumChatFormatting.RED + "Plaintext authentication was attempted but no user confirmation has been given to proceed")));
return false;
}
}else if(authType == HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256) {
SHA256Digest digest = new SHA256Digest();
int passLen = password.length();
digest.update((byte)((passLen >>> 8) & 0xFF));
digest.update((byte)(passLen & 0xFF));
for(int i = 0; i < passLen; ++i) {
char codePoint = password.charAt(i);
digest.update((byte)((codePoint >>> 8) & 0xFF));
digest.update((byte)(codePoint & 0xFF));
}
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_SAVE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 0, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 32, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
d.writeByte(32);
d.write(hashed);
}else if(authType == HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
SHA256Digest digest = new SHA256Digest();
byte[] passwd = password.getBytes(StandardCharsets.UTF_8);
digest.update(passwd, 0, passwd.length);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
byte[] toHexAndSalt = new byte[64];
for(int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
digest.reset();
digest.update(toHexAndSalt, 0, 64);
digest.update(salt, 0, salt.length);
digest.doFinal(hashed, 0);
for(int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
d.writeByte(64);
d.write(toHexAndSalt);
}else {
logger.error("Unsupported authentication type: {}", authType);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed",
new ChatComponentText(EnumChatFormatting.RED + "Unsupported authentication type: " + authType + "\n\n" + EnumChatFormatting.GRAY + "(Use a newer version of the client)")));
return false;
}
}else {
d.writeByte(0);
}
if(protocolVersion >= protocolV4) {
d.writeBoolean(enableCookies);
if(enableCookies && cookieData != null) {
d.writeByte(cookieData.length);
d.write(cookieData);
}else {
d.writeByte(0);
}
}
client.send(bao.toByteArray());
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login negotiation response!");
return false;
}
di = new DataInputStream(new EaglerInputStream(read));
type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN) {
msgLen = di.read();
dat = new byte[msgLen];
di.read(dat);
String serverUsername = ArrayUtils.asciiString(dat);
Minecraft.getMinecraft().getSession().update(serverUsername, new EaglercraftUUID(di.readLong(), di.readLong()));
Map<String,byte[]> profileDataToSend = new HashMap<>();
if(protocolVersion >= 4) {
bao.reset();
d.writeLong(EaglercraftVersion.clientBrandUUID.msb);
d.writeLong(EaglercraftVersion.clientBrandUUID.lsb);
profileDataToSend.put("brand_uuid_v1", bao.toByteArray());
}
byte[] packetSkin = EaglerProfile.getSkinPacket(protocolVersion);
if(packetSkin.length > 0xFFFF) {
throw new IOException("Skin packet is too long: " + packetSkin.length);
}
profileDataToSend.put(protocolVersion >= 4 ? "skin_v2" : "skin_v1", packetSkin);
byte[] packetCape = EaglerProfile.getCapePacket();
if(packetCape.length > 0xFFFF) {
throw new IOException("Cape packet is too long: " + packetCape.length);
}
profileDataToSend.put("cape_v1", packetCape);
byte[] packetSignatureData = UpdateService.getClientSignatureData();
if(packetSignatureData != null) {
profileDataToSend.put("update_cert_v1", packetSignatureData);
}
if(protocolVersion >= 4) {
List<Entry<String,byte[]>> toSend = new ArrayList<>(profileDataToSend.entrySet());
while(!toSend.isEmpty()) {
int sendLen = 2;
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
d.writeByte(0); // will be replaced
int packetCount = 0;
while(!toSend.isEmpty() && packetCount < 255) {
Entry<String,byte[]> etr = toSend.get(toSend.size() - 1);
int i = 3 + etr.getKey().length() + etr.getValue().length;
if(sendLen + i < 0xFF00) {
String profileDataType = etr.getKey();
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] data = etr.getValue();
d.writeShort(data.length);
d.write(data);
toSend.remove(toSend.size() - 1);
++packetCount;
}else {
break;
}
}
byte[] send = bao.toByteArray();
send[1] = (byte)packetCount;
client.send(send);
}
}else {
for(Entry<String,byte[]> etr : profileDataToSend.entrySet()) {
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
String profileDataType = etr.getKey();
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] data = etr.getValue();
d.writeShort(data.length);
d.write(data);
client.send(bao.toByteArray());
}
}
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_FINISH_LOGIN);
client.send(bao.toByteArray());
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login confirmation response!");
return false;
}
di = new DataInputStream(new EaglerInputStream(read));
type = di.read();
if(type == HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN) {
return true;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN) {
if(protocolVersion == protocolV2) {
msgLen = di.read();
}else {
msgLen = di.readUnsignedShort();
}
dat = new byte[msgLen];
di.read(dat);
String errStr = new String(dat, StandardCharsets.UTF_8);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", IChatComponent.Serializer.jsonToComponent(errStr)));
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, client, connecting, ret, di, true);
return false;
}else {
return false;
}
}catch(Throwable t) {
logger.error("Exception in handshake");
logger.error(t);
return false;
}
}
private static byte[] awaitNextPacket(IWebSocketClient client, long timeout) {
long millis = EagRuntime.steadyTimeMillis();
IWebSocketFrame b;
while((b = client.getNextBinaryFrame()) == null) {
if(client.getState().isClosed()) {
return null;
}
EagUtils.sleep(50);
if(EagRuntime.steadyTimeMillis() - millis > timeout) {
client.close();
return null;
}
}
return b.getByteArray();
}
private static void showError(Minecraft mc, IWebSocketClient client, GuiConnecting connecting, GuiScreen scr, DataInputStream err, boolean v2) throws IOException {
int errorCode = err.read();
int msgLen = v2 ? err.read() : err.readUnsignedShort();
// workaround for bug in EaglerXBungee 1.2.7 and below
if(msgLen == 0) {
if(v2) {
if(err.available() == 256) {
msgLen = 256;
}
}else {
if(err.available() == 65536) {
msgLen = 65536;
}
}
}
byte[] dat = new byte[msgLen];
err.read(dat);
String errStr = new String(dat, StandardCharsets.UTF_8);
logger.info("Server Error Code {}: {}", errorCode, errStr);
if(errorCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_BLOCKED) {
RateLimitTracker.registerBlock(client.getCurrentURI());
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(scr));
}else if(errorCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_LOCKED) {
RateLimitTracker.registerLockOut(client.getCurrentURI());
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(scr));
}else if(errorCode == HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE) {
mc.displayGuiScreen(new GuiDisconnected(scr, "connect.failed", IChatComponent.Serializer.jsonToComponent(errStr)));
}else if(connecting != null && errorCode == HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED) {
mc.displayGuiScreen(new GuiAuthenticationScreen(connecting, scr, errStr));
}else {
mc.displayGuiScreen(new GuiDisconnected(scr, "connect.failed", new ChatComponentText("Server Error Code " + errorCode + "\n" + errStr)));
}
}
public static GuiScreen displayAuthProtocolConfirm(int protocol, GuiScreen no, GuiScreen yes) {
if(protocol == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
return new GuiHandshakeApprove("plaintext", no, yes);
}else if(protocol != HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256 && protocol != HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
return new GuiHandshakeApprove("unsupportedAuth", no);
}else {
return null;
}
}
private static final byte[] HEX = new byte[] {
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* Copyright (c) 2022-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -22,6 +22,8 @@ import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.ServerCapabilities;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.message.InjectedMessageController;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.INetHandler;
import net.minecraft.network.Packet;
@ -38,6 +40,9 @@ public abstract class EaglercraftNetworkManager {
protected String pluginBrand = null;
protected String pluginVersion = null;
protected InjectedMessageController injectedController = null;
protected ServerCapabilities serverCapabilities = null;
public static final Logger logger = LogManager.getLogger("NetworkManager");
@ -45,10 +50,17 @@ public abstract class EaglercraftNetworkManager {
this.address = address;
this.temporaryBuffer = new PacketBuffer(Unpooled.buffer(0x1FFFF));
}
public void setPluginInfo(String pluginBrand, String pluginVersion) {
public void setPluginInfo(String pluginBrand, String pluginVersion, ServerCapabilities serverCapabilities) {
this.pluginBrand = pluginBrand;
this.pluginVersion = pluginVersion;
this.serverCapabilities = serverCapabilities;
}
public void setLANInfo(int protocolVer) {
this.pluginBrand = "integrated";
this.pluginVersion = "v" + protocolVer;
this.serverCapabilities = ServerCapabilities.getLAN();
}
public String getPluginBrand() {
@ -59,6 +71,14 @@ public abstract class EaglercraftNetworkManager {
return pluginVersion;
}
public ServerCapabilities getServerCapabilities() {
return serverCapabilities;
}
public void setInjectedMessageController(InjectedMessageController controller) {
injectedController = controller;
}
public abstract void connect();
public abstract EnumEaglerConnectionState getConnectStatus();
@ -109,5 +129,7 @@ public abstract class EaglercraftNetworkManager {
}
}
}
public abstract void injectRawFrame(byte[] data);
}

View File

@ -104,4 +104,14 @@ public class GuiHandshakeApprove extends GuiScreen {
}
}
public static GuiScreen displayAuthProtocolConfirm(int protocol, GuiScreen no, GuiScreen yes) {
if(protocol == HandshakePacketTypes.AUTH_METHOD_PLAINTEXT) {
return new GuiHandshakeApprove("plaintext", no, yes);
}else if(protocol != HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256 && protocol != HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256) {
return new GuiHandshakeApprove("unsupportedAuth", no);
}else {
return null;
}
}
}

View File

@ -29,12 +29,15 @@ public class HandshakePacketTypes {
public static final int PROTOCOL_CLIENT_PROFILE_DATA = 0x07;
public static final int PROTOCOL_CLIENT_FINISH_LOGIN = 0x08;
public static final int PROTOCOL_SERVER_FINISH_LOGIN = 0x09;
public static final int PROTOCOL_SERVER_REDIRECT_TO = 0x0A;
public static final int PROTOCOL_SERVER_ERROR = 0xFF;
public static final int STATE_OPENED = 0x00;
public static final int STATE_CLIENT_VERSION = 0x01;
public static final int STATE_CLIENT_LOGIN = 0x02;
public static final int STATE_CLIENT_COMPLETE = 0x03;
public static final int STATE_NEW = 0x00;
public static final int STATE_OPENED = 0x01;
public static final int STATE_CLIENT_VERSION = 0x02;
public static final int STATE_CLIENT_LOGIN = 0x03;
public static final int STATE_CLIENT_COMPLETE = 0x04;
public static final int STATE_FINISHED = 0x05;
public static final int SERVER_ERROR_UNKNOWN_PACKET = 0x01;
public static final int SERVER_ERROR_INVALID_PACKET = 0x02;

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022-2024 lax1dude. All Rights Reserved.
* Copyright (c) 2022-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -71,6 +71,11 @@ public class WebSocketNetworkManager extends EaglercraftNetworkManager {
++debugPacketCounter;
try {
byte[] asByteArray = next.getByteArray();
if(injectedController != null && injectedController.handlePacket(asByteArray, 0)) {
continue;
}
ByteBuf nettyBuffer = Unpooled.buffer(asByteArray, asByteArray.length);
nettyBuffer.writerIndex(asByteArray.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
@ -150,4 +155,13 @@ public class WebSocketNetworkManager extends EaglercraftNetworkManager {
}
}
@Override
public void injectRawFrame(byte[] data) {
if(!isChannelOpen()) {
logger.error("Frame was injected on a closed connection");
return;
}
webSocketClient.send(data);
}
}

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.minecraft.client.network.NetHandlerPlayClient;
public abstract class ClientMessageHandler implements GameMessageHandler {
protected final NetHandlerPlayClient netHandler;
public ClientMessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
}
public static ClientMessageHandler createClientHandler(int version, NetHandlerPlayClient netHandler) {
switch(version) {
case 3:
return new ClientV3MessageHandler(netHandler);
case 4:
return new ClientV4MessageHandler(netHandler);
case 5:
return new ClientV5MessageHandler(netHandler);
default:
throw new UnsupportedOperationException();
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -20,20 +20,16 @@ import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV3MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public class ClientV3MessageHandler extends ClientMessageHandler {
public ClientV3MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
super(netHandler);
}
public void handleServer(SPacketEnableFNAWSkinsEAG packet) {
@ -44,35 +40,19 @@ public class ClientV3MessageHandler implements GameMessageHandler {
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
netHandler.getCapeCache().cacheCapeCustom(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.customCape);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
netHandler.getCapeCache().cacheCapePreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetCape);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinCustomV3EAG packet) {
EaglercraftUUID responseUUID = new EaglercraftUUID(packet.uuidMost, packet.uuidLeast);
SkinModel modelId;
if(packet.modelID == (byte)0xFF) {
modelId = this.netHandler.getSkinCache().getRequestedSkinType(responseUUID);
}else {
modelId = SkinModel.getModelFromId(packet.modelID & 0x7F);
if((packet.modelID & 0x80) != 0 && modelId.sanitize) {
modelId = SkinModel.STEVE;
}
}
if(modelId.highPoly != null) {
modelId = SkinModel.STEVE;
}
this.netHandler.getSkinCache().cacheSkinCustom(responseUUID, packet.customSkin, modelId);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
this.netHandler.getSkinCache().cacheSkinPreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetSkin);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketUpdateCertEAG packet) {

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2024-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -16,81 +16,37 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.ClientUUIDLoadingCache;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.PauseMenuCustomizeState;
import net.lax1dude.eaglercraft.v1_8.cookie.ServerCookieDataStore;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.profile.SkinModel;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.skin_cache.ServerTextureCache;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SkinPacketVersionCache;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.lax1dude.eaglercraft.v1_8.webview.ServerInfoCache;
import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController;
import net.minecraft.client.Minecraft;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV4MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public class ClientV4MessageHandler extends ClientV3MessageHandler {
public ClientV4MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
super(netHandler);
}
public void handleServer(SPacketEnableFNAWSkinsEAG packet) {
netHandler.currentFNAWSkinAllowedState = packet.enableSkins;
netHandler.currentFNAWSkinForcedState = packet.force;
Minecraft.getMinecraft().getRenderManager().setEnableFNAWSkins(netHandler.currentFNAWSkinForcedState
|| (netHandler.currentFNAWSkinAllowedState && Minecraft.getMinecraft().gameSettings.enableFNAWSkins));
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
netHandler.getCapeCache().cacheCapeCustom(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.customCape);
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
netHandler.getCapeCache().cacheCapePreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
packet.presetCape);
public void handleServer(SPacketOtherSkinCustomV3EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinCustomV4EAG packet) {
EaglercraftUUID responseUUID = new EaglercraftUUID(packet.uuidMost, packet.uuidLeast);
SkinModel modelId;
if(packet.modelID == (byte)0xFF) {
modelId = this.netHandler.getSkinCache().getRequestedSkinType(responseUUID);
}else {
modelId = SkinModel.getModelFromId(packet.modelID & 0x7F);
if((packet.modelID & 0x80) != 0 && modelId.sanitize) {
modelId = SkinModel.STEVE;
}
}
if(modelId.highPoly != null) {
modelId = SkinModel.STEVE;
}
this.netHandler.getSkinCache().cacheSkinCustom(responseUUID, SkinPacketVersionCache.convertToV3Raw(packet.customSkin), modelId);
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
this.netHandler.getSkinCache().cacheSkinPreset(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.presetSkin);
}
public void handleServer(SPacketUpdateCertEAG packet) {
if (EagRuntime.getConfiguration().allowUpdateSvc()) {
UpdateService.addCertificateToSet(packet.updateCert);
}
}
public void handleServer(SPacketVoiceSignalAllowedEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeAllowed(packet.allowed, packet.iceServers);
}
public void handleServer(SPacketVoiceSignalConnectV3EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketVoiceSignalConnectV4EAG packet) {
@ -105,34 +61,6 @@ public class ClientV4MessageHandler implements GameMessageHandler {
}
}
public void handleServer(SPacketVoiceSignalDescEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeDescription(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
new String(packet.desc, StandardCharsets.UTF_8));
}
}
public void handleServer(SPacketVoiceSignalDisconnectPeerEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeDisconnect(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
}
public void handleServer(SPacketVoiceSignalGlobalEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeGlobalNew(packet.users);
}
}
public void handleServer(SPacketVoiceSignalICEEAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeICECandidate(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast),
new String(packet.ice, StandardCharsets.UTF_8));
}
}
public void handleServer(SPacketForceClientSkinPresetV4EAG packet) {
EaglerProfile.handleForceSkinPreset(packet.presetSkin);
}
@ -166,14 +94,10 @@ public class ClientV4MessageHandler implements GameMessageHandler {
public void handleServer(SPacketInvalidatePlayerCacheV4EAG packet) {
if(packet.players != null && packet.players.size() > 0) {
ServerTextureCache textureCache = this.netHandler.getTextureCache();
for(SPacketInvalidatePlayerCacheV4EAG.InvalidateRequest req : packet.players) {
EaglercraftUUID uuid = new EaglercraftUUID(req.uuidMost, req.uuidLeast);
if(req.invalidateSkin) {
this.netHandler.getSkinCache().handleInvalidate(uuid);
}
if(req.invalidateCape) {
this.netHandler.getCapeCache().handleInvalidate(uuid);
}
textureCache.dropPlayer(new EaglercraftUUID(req.uuidMost, req.uuidLeast),
req.invalidateSkin, req.invalidateCape);
}
}
}

View File

@ -0,0 +1,113 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.WrongPacketException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.server.*;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenPhishingWarning;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenRecieveServerInfo;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenRequestDisplay;
import net.lax1dude.eaglercraft.v1_8.webview.GuiScreenServerInfo;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.network.NetHandlerPlayClient;
public class ClientV5MessageHandler extends ClientV4MessageHandler {
public ClientV5MessageHandler(NetHandlerPlayClient netHandler) {
super(netHandler);
}
public void handleServer(SPacketOtherSkinPresetEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinCustomV4EAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherCapePresetEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherCapeCustomEAG packet) {
throw new WrongPacketException();
}
public void handleServer(SPacketOtherSkinPresetV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherSkinCustomV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapePresetV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherCapeCustomV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketOtherTexturesV5EAG packet) {
netHandler.getTextureCache().handlePacket(packet);
}
public void handleServer(SPacketClientStateFlagV5EAG packet) {
StateFlags.setFlag(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.state);
}
public void handleServer(SPacketDisplayWebViewURLV5EAG packet) {
if (netHandler.allowedDisplayWebview && !netHandler.allowedDisplayWebviewYes) {
return;
}
Minecraft mc = Minecraft.getMinecraft();
GuiScreen screen = GuiScreenServerInfo.createForDisplayRequest(mc.currentScreen, packet.flags,
packet.embedTitle, packet.embedURL);
if (!mc.gameSettings.hasHiddenPhishWarning && !GuiScreenPhishingWarning.hasShownMessage) {
screen = new GuiScreenPhishingWarning(screen);
}
if (!netHandler.allowedDisplayWebview) {
mc.displayGuiScreen(new GuiScreenRequestDisplay(screen, mc.currentScreen, netHandler));
} else {
mc.displayGuiScreen(screen);
}
}
public void handleServer(SPacketDisplayWebViewBlobV5EAG packet) {
if (netHandler.allowedDisplayWebview && !netHandler.allowedDisplayWebviewYes) {
return;
}
Minecraft mc = Minecraft.getMinecraft();
GuiScreen screen = new GuiScreenRecieveServerInfo(mc.currentScreen, packet.embedHash,
(parent, blob, permissionsOriginUUID) -> {
return GuiScreenServerInfo.createForDisplayRequest(parent, packet.flags, packet.embedTitle, blob,
permissionsOriginUUID);
});
if (!mc.gameSettings.hasHiddenPhishWarning && !GuiScreenPhishingWarning.hasShownMessage) {
screen = new GuiScreenPhishingWarning(screen);
}
if (!netHandler.allowedDisplayWebview) {
mc.displayGuiScreen(new GuiScreenRequestDisplay(screen, mc.currentScreen, netHandler));
} else {
mc.displayGuiScreen(screen);
}
}
}

View File

@ -1,207 +0,0 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.io.IOException;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol.ServerV3MessageHandler;
import net.lax1dude.eaglercraft.v1_8.sp.server.socket.protocol.ServerV4MessageHandler;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.network.NetHandlerPlayServer;
import net.minecraft.network.PacketBuffer;
public class GameProtocolMessageController {
private static final Logger logger = LogManager.getLogger("GameProtocolMessageController");
public final GamePluginMessageProtocol protocol;
public final int sendDirection;
public final int receiveDirection;
private final PacketBufferInputWrapper inputStream = new PacketBufferInputWrapper(null);
private final PacketBufferOutputWrapper outputStream = new PacketBufferOutputWrapper(null);
private final GameMessageHandler handler;
private final IPluginMessageSendFunction sendFunction;
private final List<PacketBuffer> sendQueueV4;
private final boolean noDelay;
public GameProtocolMessageController(GamePluginMessageProtocol protocol, int sendDirection, GameMessageHandler handler,
IPluginMessageSendFunction sendCallback) {
this.protocol = protocol;
this.sendDirection = sendDirection;
this.receiveDirection = GamePluginMessageConstants.oppositeDirection(sendDirection);
this.handler = handler;
this.sendFunction = sendCallback;
this.noDelay = protocol.ver < 4 || EagRuntime.getConfiguration().isEaglerNoDelay();
this.sendQueueV4 = !noDelay ? new LinkedList<>() : null;
}
public boolean handlePacket(String channel, PacketBuffer data) throws IOException {
GameMessagePacket pkt;
if(protocol.ver >= 4 && data.readableBytes() > 0 && data.getByte(data.readerIndex()) == (byte) 0xFF
&& channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
data.readByte();
inputStream.buffer = data;
int count = inputStream.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = data.readVarIntFromBuffer();
k = data.readerIndex() + j;
if(j > data.readableBytes()) {
throw new IOException("Packet fragment is too long: " + j + " > " + data.readableBytes());
}
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null) {
try {
pkt.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", pkt.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
}else {
logger.warn("Could not read packet fragment {} of {}, unknown packet", count, i);
}
if(data.readerIndex() != k) {
logger.warn("Packet fragment {} was the wrong length: {} != {}",
(pkt != null ? pkt.getClass().getSimpleName() : "unknown"), j + data.readerIndex() - k, j);
data.readerIndex(k);
}
}
if(data.readableBytes() > 0) {
logger.warn("Leftover data after reading multi-packet! ({} bytes)", data.readableBytes());
}
inputStream.buffer = null;
return true;
}
inputStream.buffer = data;
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null && inputStream.available() > 0) {
logger.warn("Leftover data after reading packet {}! ({} bytes)", pkt.getClass().getSimpleName(), inputStream.available());
}
inputStream.buffer = null;
if(pkt != null) {
try {
pkt.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", pkt.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
return true;
}else {
return false;
}
}
public void sendPacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
String chan = protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && j + 1 != len) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
if(sendQueueV4 != null && chan.equals(GamePluginMessageConstants.V4_CHANNEL)) {
sendQueueV4.add(buf);
}else {
sendFunction.sendPluginMessage(chan, buf);
}
}
public void flush() {
if(sendQueueV4 != null) {
int queueLen = sendQueueV4.size();
PacketBuffer pkt;
if(queueLen == 0) {
return;
}else if(queueLen == 1) {
pkt = sendQueueV4.remove(0);
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, pkt);
}else {
int i, j, sendCount, totalLen, lastLen;
PacketBuffer sendBuffer;
while(sendQueueV4.size() > 0) {
sendCount = 0;
totalLen = 0;
Iterator<PacketBuffer> itr = sendQueueV4.iterator();
do {
i = itr.next().readableBytes();
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && itr.hasNext());
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
pkt = sendQueueV4.remove(0);
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, pkt);
continue;
}
sendBuffer = new PacketBuffer(Unpooled.buffer(1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)));
sendBuffer.writeByte(0xFF);
sendBuffer.writeVarIntToBuffer(sendCount);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
sendBuffer.writeVarIntToBuffer(pkt.readableBytes());
sendBuffer.writeBytes(pkt);
}
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, sendBuffer);
}
}
}
}
public static GameMessageHandler createClientHandler(int protocolVersion, NetHandlerPlayClient netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ClientV3MessageHandler(netHandler);
case 4:
return new ClientV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, NetHandlerPlayServer netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(netHandler);
case 4:
return new ServerV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
}

View File

@ -0,0 +1,53 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
public class StateFlags {
public static final EaglercraftUUID EAGLER_PLAYER_FLAG_PRESENT = new EaglercraftUUID(0x55F63601694140D9L,
0xB77BCE7B99A62E52L);
public static final EaglercraftUUID LEGACY_EAGLER_PLAYER_FLAG_PRESENT = new EaglercraftUUID(0xEEEEA64771094C4EL,
0x86E55B81D17E67EBL);
public static final EaglercraftUUID DISABLE_SKIN_URL_LOOKUP = new EaglercraftUUID(0xC41D641BE2DA4094L,
0xB1B2DFF2E9D08180L);
public static boolean eaglerPlayerFlag = false;
public static boolean eaglerPlayerFlagSupervisor = false;
public static boolean disableSkinURLLookup = false;
public static void setFlag(EaglercraftUUID flag, int value) {
if (flag.equals(EAGLER_PLAYER_FLAG_PRESENT)) {
eaglerPlayerFlag = (value & 1) != 0;
eaglerPlayerFlagSupervisor = (value & 2) != 0;
} else if (flag.equals(DISABLE_SKIN_URL_LOOKUP)) {
disableSkinURLLookup = value != 0;
}
}
public static void reset() {
eaglerPlayerFlag = false;
eaglerPlayerFlagSupervisor = false;
disableSkinURLLookup = false;
}
}

View File

@ -0,0 +1,98 @@
/*
* Copyright (c) 2022-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.crypto.SHA256Digest;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
public class AuthTypes {
public static byte[] applyEaglerSHA256(String password, byte[] salt) {
SHA256Digest digest = new SHA256Digest();
int passLen = password.length();
digest.update((byte)((passLen >>> 8) & 0xFF));
digest.update((byte)(passLen & 0xFF));
for (int i = 0; i < passLen; ++i) {
char codePoint = password.charAt(i);
digest.update((byte)((codePoint >>> 8) & 0xFF));
digest.update((byte)(codePoint & 0xFF));
}
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_SAVE, 0, 32);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 0, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
digest.reset();
digest.update(hashed, 0, 32);
digest.update(salt, 32, 32);
digest.update(HandshakePacketTypes.EAGLER_SHA256_SALT_BASE, 0, 32);
digest.doFinal(hashed, 0);
return hashed;
}
public static byte[] applyAuthMeSHA256(String password, byte[] salt) {
SHA256Digest digest = new SHA256Digest();
byte[] passwd = password.getBytes(StandardCharsets.UTF_8);
digest.update(passwd, 0, passwd.length);
byte[] hashed = new byte[32];
digest.doFinal(hashed, 0);
byte[] toHexAndSalt = new byte[64];
for (int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
digest.reset();
digest.update(toHexAndSalt, 0, 64);
digest.update(salt, 0, salt.length);
digest.doFinal(hashed, 0);
for (int i = 0; i < 32; ++i) {
toHexAndSalt[i << 1] = HEX[(hashed[i] >> 4) & 0xF];
toHexAndSalt[(i << 1) + 1] = HEX[hashed[i] & 0xF];
}
return toHexAndSalt;
}
private static final byte[] HEX = new byte[] {
(byte) '0', (byte) '1', (byte) '2', (byte) '3', (byte) '4', (byte) '5', (byte) '6', (byte) '7',
(byte) '8', (byte) '9', (byte) 'a', (byte) 'b', (byte) 'c', (byte) 'd', (byte) 'e', (byte) 'f'
};
}

View File

@ -0,0 +1,100 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.util.ArrayList;
import java.util.List;
import com.carrotsearch.hppc.IntArrayList;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.lax1dude.eaglercraft.v1_8.voice.VoiceClientController;
import net.lax1dude.eaglercraft.v1_8.webview.WebViewOverlayController;
public class ClientCapabilities {
static ClientCapabilities createCapabilities(boolean cookie) {
ClientCapabilities caps = new ClientCapabilities();
caps.addStandard(StandardCaps.REDIRECT, 0);
caps.addStandard(StandardCaps.NOTIFICATION, 0);
caps.addStandard(StandardCaps.PAUSE_MENU, 0);
if (VoiceClientController.isClientSupported()) {
caps.addStandard(StandardCaps.VOICE, 0);
}
if (UpdateService.supported()) {
caps.addStandard(StandardCaps.UPDATE, 0);
}
if (WebViewOverlayController.supported() || WebViewOverlayController.fallbackSupported()) {
caps.addStandard(StandardCaps.WEBVIEW, 0);
}
if (cookie) {
caps.addStandard(StandardCaps.COOKIE, 0);
}
return caps;
}
static class ExtCapability {
final EaglercraftUUID uuid;
final int vers;
ExtCapability(EaglercraftUUID uuid, int vers) {
this.uuid = uuid;
this.vers = vers;
}
}
private int standardCaps = 0;
private IntArrayList standardCapsVers = new IntArrayList();
private List<ExtCapability> extendedCaps = new ArrayList<>();
int getStandardCaps() {
return standardCaps;
}
int[] getStandardCapsVers() {
return standardCapsVers.toArray();
}
ExtCapability[] getExtendedCaps() {
return extendedCaps.toArray(new ExtCapability[extendedCaps.size()]);
}
private void addStandard(int type, int... versions) {
int bit = (1 << type);
standardCapsVers.insert(Integer.bitCount(standardCaps & (bit - 1)), bits(versions));
standardCaps |= bit;
}
private void addExtended(EaglercraftUUID type, int... versions) {
extendedCaps.add(new ExtCapability(type, bits(versions)));
}
private int bits(int... versions) {
int bits = 0;
for(int i = 0; i < versions.length; ++i) {
bits |= (1 << versions[i]);
}
return bits;
}
}

View File

@ -0,0 +1,356 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.io.DataOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglerOutputStream;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.profile.GuiAuthenticationScreen;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.socket.RateLimitTracker;
import net.lax1dude.eaglercraft.v1_8.socket.WebSocketNetworkManager;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.GuiDisconnected;
import net.minecraft.client.gui.GuiScreen;
import net.minecraft.client.multiplayer.GuiConnecting;
import net.minecraft.client.network.NetHandlerPlayClient;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
public class HandshakerHandler {
static final Logger logger = LogManager.getLogger("HandshakerHandler");
protected final Minecraft mc;
protected final IWebSocketClient websocket;
protected final GuiConnecting parent;
protected final GuiScreen ret;
protected final String username;
protected final String password;
protected final boolean allowPlaintext;
protected final boolean enableCookies;
protected final byte[] cookieData;
protected HandshakerInstance handshaker;
protected boolean nicknameSelection = true;
protected int baseState = NEW;
protected WebSocketNetworkManager networkManager;
protected static final int NEW = 0, SENT_HANDSHAKE = 1, PROCESSING = 2, FINISHED = 3;
public HandshakerHandler(GuiConnecting parent, IWebSocketClient websocket, String username, String password,
boolean allowPlaintext, boolean enableCookies, byte[] cookieData) {
this.mc = GuiConnecting.getMC(parent);
this.websocket = websocket;
this.parent = parent;
this.ret = GuiConnecting.getPrevScreen(parent);
this.username = username;
this.password = password;
this.allowPlaintext = allowPlaintext;
this.enableCookies = enableCookies;
this.cookieData = cookieData;
}
private static final int protocolV3 = 3;
private static final int protocolV4 = 4;
private static final int protocolV5 = 5;
public static byte[] getSPHandshakeProtocolData() {
try {
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
d.writeShort(3); // supported eagler protocols count
d.writeShort(protocolV3); // client supports v3
d.writeShort(protocolV4); // client supports v4
d.writeShort(protocolV5); // client supports v5
return bao.toByteArray();
}catch(IOException ex) {
throw new RuntimeException(ex);
}
}
public void tick() {
if(baseState == NEW) {
if(websocket.isClosed()) {
handleError("Connection Closed", null);
return;
}
baseState = SENT_HANDSHAKE;
beginHandshake();
}else if(baseState == SENT_HANDSHAKE) {
IWebSocketFrame frame = websocket.getNextBinaryFrame();
if(frame != null) {
byte[] data = frame.getByteArray();
handleServerHandshake(new PacketBuffer(Unpooled.buffer(data, data.length).writerIndex(data.length)));
}
}else if(baseState == PROCESSING) {
handshaker.tick();
}else if(baseState == FINISHED) {
if(networkManager != null) {
try {
networkManager.processReceivedPackets();
} catch (IOException e) {
}
}
}
}
protected void beginHandshake() {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_VERSION);
buffer.writeByte(2); // legacy protocol version
buffer.writeBytes(getSPHandshakeProtocolData()); // write supported eagler protocol versions
buffer.writeShort(1); // supported game protocols count
buffer.writeShort(47); // client supports 1.8 protocol
String clientBrand = EaglercraftVersion.projectForkName;
buffer.writeByte(clientBrand.length());
writeASCII(buffer, clientBrand);
String clientVers = EaglercraftVersion.projectOriginVersion;
buffer.writeByte(clientVers.length());
writeASCII(buffer, clientVers);
buffer.writeBoolean(password != null);
buffer.writeByte(username.length());
writeASCII(buffer, username);
websocket.send(buffer.toBytes());
}
protected static void writeASCII(PacketBuffer buffer, String str) {
for(int i = 0, l = str.length(); i < l; ++i) {
buffer.writeByte(str.charAt(i));
}
}
protected void handleServerHandshake(PacketBuffer packet) {
try {
int pktId = packet.readUnsignedByte();
switch(pktId) {
case HandshakePacketTypes.PROTOCOL_SERVER_VERSION:
handleServerVersion(packet);
break;
case HandshakePacketTypes.PROTOCOL_VERSION_MISMATCH:
handleVersionMismatch(packet);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_ERROR:
handleServerError(packet, false);
break;
default:
handleError("connect.failed", new ChatComponentText("Unknown packet type " + pktId + " received"));
break;
}
}catch(Exception ex) {
handleError("connect.failed", new ChatComponentText("Invalid packet received"));
logger.error("Invalid packet received");
logger.error(ex);
}
}
protected void handleServerVersion(PacketBuffer packet) {
int protocolVersion = packet.readUnsignedShort();
if(protocolVersion != protocolV3 && protocolVersion != protocolV4 && protocolVersion != protocolV5) {
logger.info("Incompatible server version: {}", protocolVersion);
handleError("connect.failed", new ChatComponentText(protocolVersion < protocolV3 ? "Outdated Server" : "Outdated Client"));
return;
}
int gameVers = packet.readUnsignedShort();
if(gameVers != 47) {
logger.info("Incompatible minecraft protocol version: {}", gameVers);
handleError("connect.failed", new ChatComponentText("This server does not support 1.8!"));
return;
}
logger.info("Server protocol: {}", protocolVersion);
int msgLen = packet.readUnsignedByte();
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String pluginBrand = ArrayUtils.asciiString(dat);
msgLen = packet.readUnsignedByte();
dat = new byte[msgLen];
packet.readBytes(dat);
String pluginVersion = ArrayUtils.asciiString(dat);
logger.info("Server version: {}", pluginVersion);
logger.info("Server brand: {}", pluginBrand);
int authType = packet.readUnsignedByte();
int saltLength = (int)packet.readUnsignedShort() & 0xFFFF;
byte[] salt = new byte[saltLength];
packet.readBytes(salt);
if(protocolVersion >= protocolV5) {
nicknameSelection = packet.readBoolean();
}
baseState = PROCESSING;
switch(protocolVersion) {
case protocolV3:
handshaker = new HandshakerV3(this);
break;
case protocolV4:
handshaker = new HandshakerV4(this);
break;
case protocolV5:
handshaker = new HandshakerV5(this);
break;
}
handshaker.begin(pluginBrand, pluginVersion, authType, salt);
}
protected void handleVersionMismatch(PacketBuffer packet) {
StringBuilder protocols = new StringBuilder();
int c = packet.readUnsignedShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
protocols.append(", ");
}
protocols.append("v").append(packet.readUnsignedShort());
}
StringBuilder games = new StringBuilder();
c = packet.readUnsignedShort();
for(int i = 0; i < c; ++i) {
if(i > 0) {
games.append(", ");
}
games.append("mc").append(packet.readUnsignedShort());
}
logger.info("Incompatible client: v3/v4/v5 & mc47");
logger.info("Server supports: {}", protocols);
logger.info("Server supports: {}", games);
int msgLen = packet.readUnsignedByte();
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
handleError("connect.failed", new ChatComponentText(msg));
}
protected void handleServerError(PacketBuffer packet, boolean v3) {
int errCode = packet.readUnsignedByte();
int msgLen;
if(v3) {
msgLen = packet.readUnsignedShort();
if(msgLen == 0 && packet.readableBytes() == 65536) {
// workaround for bug in EaglerXBungee 1.2.7 and below
msgLen = 65536;
}
}else {
msgLen = packet.readUnsignedByte();
if(msgLen == 0 && packet.readableBytes() == 256) {
// workaround for bug in EaglerXBungee 1.2.7 and below
msgLen = 256;
}
}
byte[] dat = new byte[msgLen];
packet.readBytes(dat);
String msg = new String(dat, StandardCharsets.UTF_8);
if(errCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_BLOCKED) {
handleRatelimit(false, new ChatComponentText(msg));
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_LOCKED) {
handleRatelimit(true, new ChatComponentText(msg));
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_AUTHENTICATION_REQUIRED) {
handleAuthRequired(msg);
}else if(errCode == HandshakePacketTypes.SERVER_ERROR_CUSTOM_MESSAGE) {
handleError("connect.failed", v3 ? IChatComponent.Serializer.jsonToComponent(msg) : new ChatComponentText(msg));
}else {
handleError("connect.failed", new ChatComponentText("Server Error Code " + errCode + "\n" + msg));
}
}
protected void handleSuccess() {
if(baseState != FINISHED) {
baseState = FINISHED;
websocket.setEnableStringFrames(false);
websocket.clearStringFrames();
networkManager = new WebSocketNetworkManager(websocket);
networkManager.setPluginInfo(handshaker.pluginBrand, handshaker.pluginVersion, new ServerCapabilities(
handshaker.serverStandardCaps, handshaker.serverStandardCapVers, handshaker.extendedCaps));
mc.bungeeOutdatedMsgTimer = 80;
mc.clearTitles();
mc.getSession().update(handshaker.username, handshaker.uuid);
networkManager.setConnectionState(EnumConnectionState.PLAY);
new NetHandlerPlayClient(this.mc, ret, networkManager, this.mc.getSession().getProfile(),
GamePluginMessageProtocol.getByVersion(handshaker.getVersion()));
}
}
protected void handleServerRedirectTo(String address) {
mc.handleReconnectPacket(address);
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(ret);
}
}
protected void handleRatelimit(boolean locked, IChatComponent detail) {
if(locked) {
RateLimitTracker.registerLockOut(websocket.getCurrentURI());
}else {
RateLimitTracker.registerBlock(websocket.getCurrentURI());
}
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(ret));
}
}
protected void handleError(String message, IChatComponent detail) {
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(new GuiDisconnected(ret, message, detail != null ? detail : new ChatComponentText("")));
}
}
protected void handleAuthRequired(String message) {
websocket.close();
if(baseState != FINISHED) {
baseState = FINISHED;
mc.displayGuiScreen(new GuiAuthenticationScreen(parent, ret, message));
}
}
}

View File

@ -0,0 +1,224 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.EaglercraftVersion;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.profile.EaglerProfile;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.update.UpdateService;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentText;
import net.minecraft.util.IChatComponent;
public abstract class HandshakerInstance {
protected final HandshakerHandler handler;
protected String pluginBrand;
protected String pluginVersion;
protected String username;
protected EaglercraftUUID uuid;
protected int state = HandshakePacketTypes.STATE_NEW;
protected int serverStandardCaps;
protected byte[] serverStandardCapVers;
protected ObjectByteMap<EaglercraftUUID> extendedCaps;
public HandshakerInstance(HandshakerHandler handler) {
this.handler = handler;
}
protected void begin(String pluginBrand, String pluginVersion, int authType, byte[] salt) {
this.pluginBrand = pluginBrand;
this.pluginVersion = pluginVersion;
byte[] password = null;
if (handler.password != null) {
switch(authType) {
case HandshakePacketTypes.AUTH_METHOD_NONE:
break;
case HandshakePacketTypes.AUTH_METHOD_EAGLER_SHA256:
password = AuthTypes.applyEaglerSHA256(handler.password, salt);
break;
case HandshakePacketTypes.AUTH_METHOD_AUTHME_SHA256:
password = AuthTypes.applyAuthMeSHA256(handler.password, salt);
break;
case HandshakePacketTypes.AUTH_METHOD_PLAINTEXT:
if(!handler.allowPlaintext) {
handleError("disconnect.loginFailed", new ChatComponentText("Server attempted insecure plaintext authentication without user consent!"));
return;
}
password = handler.password.getBytes(StandardCharsets.UTF_8);
if(password.length > 255) {
handleError("disconnect.loginFailed", new ChatComponentText("Password is too long!"));
return;
}
break;
default:
handleError("disconnect.loginFailed", new ChatComponentText("Unknown auth method #" + authType + " requested"));
return;
}
}
sendClientRequestLogin(handler.username, "default", password, handler.enableCookies, handler.cookieData);
state = HandshakePacketTypes.STATE_CLIENT_LOGIN;
}
protected abstract int getVersion();
protected abstract void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie);
protected void tick() {
IWebSocketFrame frame;
while (state != HandshakePacketTypes.STATE_FINISHED
&& (frame = handler.websocket.getNextBinaryFrame()) != null) {
handleInboundPacket(frame.getByteArray());
}
if(handler.websocket.isClosed()) {
handleError("Connection Closed", (IChatComponent) null);
}
}
protected void handleInboundPacket(byte[] data) {
try {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer(data, data.length).writerIndex(data.length));
int pktId = buffer.readUnsignedByte();
switch(pktId) {
case HandshakePacketTypes.PROTOCOL_SERVER_ALLOW_LOGIN:
handleInboundServerAllowLogin(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN:
handleInboundServerDenyLogin(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN:
handleServerFinishLogin();
break;
case HandshakePacketTypes.PROTOCOL_SERVER_REDIRECT_TO:
handleInboundServerRedirectTo(buffer);
break;
case HandshakePacketTypes.PROTOCOL_SERVER_ERROR:
handleInboundServerError(buffer);
break;
default:
handleError("connect.failed", "Unknown packet type " + pktId + " received");
break;
}
}catch(Exception ex) {
handler.handleError("connect.failed", new ChatComponentText("Invalid packet received"));
HandshakerHandler.logger.error("Invalid packet received");
HandshakerHandler.logger.error(ex);
}
}
protected void handleError(String message, String detail) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleError(message, IChatComponent.Serializer.jsonToComponent(detail));
}
protected void handleError(String message, IChatComponent detail) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleError(message, detail);
}
protected abstract void handleInboundServerAllowLogin(PacketBuffer buffer);
protected abstract void handleInboundServerDenyLogin(PacketBuffer buffer);
protected void handleServerAllowLogin(String username, EaglercraftUUID uuid, int serverStandardCaps,
byte[] serverStandardCapVers, ObjectByteMap<EaglercraftUUID> extendedCaps) {
if(state != HandshakePacketTypes.STATE_CLIENT_LOGIN) {
handleError("connect.failed", "Unexpected allow login packet in state " + state);
return;
}
this.username = username;
this.uuid = uuid;
this.serverStandardCaps = serverStandardCaps;
this.serverStandardCapVers = serverStandardCapVers;
this.extendedCaps = extendedCaps;
Map<String, byte[]> profileDataToSend = new HashMap<>();
if(getVersion() >= 4) {
byte[] arr = new byte[16];
ByteBuf buf = Unpooled.buffer(arr, 16);
buf.writeLong(EaglercraftVersion.clientBrandUUID.msb);
buf.writeLong(EaglercraftVersion.clientBrandUUID.lsb);
profileDataToSend.put("brand_uuid_v1", arr);
}
byte[] packetSkin = EaglerProfile.getSkinPacket(getVersion());
if(packetSkin.length > 0xFFFF) {
handleError("connect.failed", new ChatComponentText("Skin packet is too long: " + packetSkin.length));
return;
}
profileDataToSend.put(getVersion() >= 4 ? "skin_v2" : "skin_v1", packetSkin);
byte[] packetCape = EaglerProfile.getCapePacket();
if(packetCape.length > 0xFFFF) {
handleError("connect.failed", new ChatComponentText("Cape packet is too long: " + packetCape.length));
return;
}
profileDataToSend.put("cape_v1", packetCape);
byte[] packetSignatureData = UpdateService.getClientSignatureData();
if(packetSignatureData != null) {
profileDataToSend.put("update_cert_v1", packetSignatureData);
}
sendClientProfileData(profileDataToSend);
sendFinishLogin();
state = HandshakePacketTypes.STATE_CLIENT_COMPLETE;
}
protected abstract void sendClientProfileData(Map<String, byte[]> profileDataToSend);
protected abstract void sendFinishLogin();
protected void handleServerFinishLogin() {
if(state != HandshakePacketTypes.STATE_CLIENT_COMPLETE) {
handleError("connect.failed", "Unexpected finish login packet in state " + state);
return;
}
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleSuccess();
}
protected abstract void handleInboundServerRedirectTo(PacketBuffer buffer);
protected void handleServerRedirectTo(String address) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleServerRedirectTo(address);
}
protected abstract void handleInboundServerError(PacketBuffer buffer);
}

View File

@ -0,0 +1,106 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.IChatComponent;
public class HandshakerV3 extends HandshakerInstance {
public HandshakerV3(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 3;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, ServerCapabilities.VIRTUAL_V3_SERVER_CAPS,
ServerCapabilities.VIRTUAL_V3_SERVER_CAPS_VERS, null);
}
@Override
protected void handleInboundServerDenyLogin(PacketBuffer buffer) {
byte[] dat = new byte[buffer.readUnsignedShort()];
buffer.readBytes(dat);
handleError("disconnect.loginFailed",
IChatComponent.Serializer.jsonToComponent(new String(dat, StandardCharsets.UTF_8)));
}
@Override
protected void sendClientProfileData(Map<String, byte[]> profileDataToSend) {
for(Map.Entry<String, byte[]> etr : profileDataToSend.entrySet()) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
String profileDataType = etr.getKey();
buffer.writeByte(profileDataType.length());
HandshakerHandler.writeASCII(buffer, profileDataType);
byte[] data = etr.getValue();
buffer.writeShort(data.length);
buffer.writeBytes(data);
handler.websocket.send(buffer.toBytes());
}
}
@Override
protected void sendFinishLogin() {
handler.websocket.send(new byte[] { (byte) HandshakePacketTypes.PROTOCOL_CLIENT_FINISH_LOGIN });
}
@Override
protected void handleInboundServerRedirectTo(PacketBuffer buffer) {
handleError("disconnect.loginFailed", "Unexpected login redirect packet");
}
@Override
protected void handleInboundServerError(PacketBuffer buffer) {
state = HandshakePacketTypes.STATE_FINISHED;
handler.handleServerError(buffer, true);
}
}

View File

@ -0,0 +1,107 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.minecraft.network.PacketBuffer;
public class HandshakerV4 extends HandshakerV3 {
public HandshakerV4(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 4;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
buffer.writeBoolean(enableCookies);
if(enableCookies && cookie != null) {
buffer.writeByte(cookie.length);
buffer.writeBytes(cookie);
}else {
buffer.writeByte(0);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, ServerCapabilities.VIRTUAL_V4_SERVER_CAPS,
ServerCapabilities.VIRTUAL_V4_SERVER_CAPS_VERS, null);
}
@Override
protected void sendClientProfileData(Map<String, byte[]> profileDataToSend) {
List<Entry<String,byte[]>> toSend = new ArrayList<>(profileDataToSend.entrySet());
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
while(!toSend.isEmpty()) {
int sendLen = 2;
buffer.writerIndex(0);
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
buffer.writeByte(0); // will be replaced
int packetCount = 0;
while(!toSend.isEmpty() && packetCount < 255) {
Entry<String,byte[]> etr = toSend.get(toSend.size() - 1);
int i = 3 + etr.getKey().length() + etr.getValue().length;
if(sendLen + i < 0xFF00) {
String profileDataType = etr.getKey();
buffer.writeByte(profileDataType.length());
HandshakerHandler.writeASCII(buffer, profileDataType);
byte[] data = etr.getValue();
buffer.writeShort(data.length);
buffer.writeBytes(data);
toSend.remove(toSend.size() - 1);
++packetCount;
}else {
break;
}
}
byte[] send = buffer.toBytes();
send[1] = (byte)packetCount;
handler.websocket.send(send);
}
}
}

View File

@ -0,0 +1,111 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import java.nio.charset.StandardCharsets;
import com.carrotsearch.hppc.ObjectByteHashMap;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.ArrayUtils;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.HandshakePacketTypes;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake.ClientCapabilities.ExtCapability;
import net.minecraft.network.PacketBuffer;
public class HandshakerV5 extends HandshakerV4 {
public HandshakerV5(HandshakerHandler handler) {
super(handler);
}
@Override
protected int getVersion() {
return 5;
}
@Override
protected void sendClientRequestLogin(String username, String requestedServer, byte[] password,
boolean enableCookies, byte[] cookie) {
PacketBuffer buffer = new PacketBuffer(Unpooled.buffer());
buffer.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_REQUEST_LOGIN);
if(handler.nicknameSelection) {
buffer.writeByte(username.length());
HandshakerHandler.writeASCII(buffer, username);
}else {
buffer.writeByte(0);
}
buffer.writeByte(requestedServer.length());
HandshakerHandler.writeASCII(buffer, requestedServer);
if(password != null) {
buffer.writeByte(password.length);
buffer.writeBytes(password);
}else {
buffer.writeByte(0);
}
buffer.writeBoolean(enableCookies);
if(enableCookies && cookie != null) {
buffer.writeByte(cookie.length);
buffer.writeBytes(cookie);
}else {
buffer.writeByte(0);
}
ClientCapabilities caps = ClientCapabilities.createCapabilities(enableCookies);
buffer.writeVarIntToBuffer(caps.getStandardCaps());
int[] vers = caps.getStandardCapsVers();
for(int i = 0; i < vers.length; ++i) {
buffer.writeVarIntToBuffer(vers[i]);
}
ExtCapability[] extVers = caps.getExtendedCaps();
buffer.writeByte(extVers.length);
for(int i = 0; i < extVers.length; ++i) {
ExtCapability extCap = extVers[i];
buffer.writeLong(extCap.uuid.msb);
buffer.writeLong(extCap.uuid.lsb);
buffer.writeVarIntToBuffer(extCap.vers);
}
handler.websocket.send(buffer.toBytes());
}
@Override
protected void handleInboundServerAllowLogin(PacketBuffer buffer) {
byte[] username = new byte[buffer.readUnsignedByte()];
buffer.readBytes(username);
EaglercraftUUID uuid = new EaglercraftUUID(buffer.readLong(), buffer.readLong());
int standardCaps = buffer.readVarIntFromBuffer();
byte[] standardCapsVers = new byte[Integer.bitCount(standardCaps)];
buffer.readBytes(standardCapsVers);
int extCaps = buffer.readUnsignedByte();
ObjectByteMap<EaglercraftUUID> extCapsMap = null;
if(extCaps > 0) {
extCapsMap = new ObjectByteHashMap<>(extCaps);
for (int i = 0; i < extCaps; ++i) {
extCapsMap.put(new EaglercraftUUID(buffer.readLong(), buffer.readLong()), buffer.readByte());
}
}
handleServerAllowLogin(ArrayUtils.asciiString(username), uuid, standardCaps, standardCapsVers, extCapsMap);
}
@Override
protected void handleInboundServerRedirectTo(PacketBuffer buffer) {
byte[] urlLen = new byte[buffer.readShort()];
buffer.readBytes(urlLen);
handleServerRedirectTo(new String(urlLen, StandardCharsets.UTF_8));
}
}

View File

@ -0,0 +1,79 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import com.carrotsearch.hppc.ObjectByteMap;
import net.lax1dude.eaglercraft.v1_8.EaglercraftUUID;
public class ServerCapabilities {
static final int VIRTUAL_V3_SERVER_CAPS = (1 << StandardCaps.UPDATE) | (1 << StandardCaps.VOICE);
static final byte[] VIRTUAL_V3_SERVER_CAPS_VERS = new byte[] { 0, 0 };
static final int VIRTUAL_V4_SERVER_CAPS = (1 << StandardCaps.UPDATE) | (1 << StandardCaps.VOICE)
| (1 << StandardCaps.REDIRECT) | (1 << StandardCaps.NOTIFICATION) | (1 << StandardCaps.PAUSE_MENU)
| (1 << StandardCaps.WEBVIEW) | (1 << StandardCaps.COOKIE);
static final byte[] VIRTUAL_V4_SERVER_CAPS_VERS = new byte[] { 0, 0, 0, 0, 0, 0 };
private final int standardCaps;
private final byte[] standardCapVers;
private final ObjectByteMap<EaglercraftUUID> extendedCaps;
public ServerCapabilities(int standardCaps, byte[] standardCapVers, ObjectByteMap<EaglercraftUUID> extendedCaps) {
this.standardCaps = standardCaps;
this.standardCapVers = standardCapVers;
this.extendedCaps = extendedCaps;
}
public boolean hasCapability(int cap, int ver) {
int bit = 1 << cap;
if((standardCaps & bit) != 0) {
int versIndex = Integer.bitCount(standardCaps & (bit - 1));
if(versIndex < standardCapVers.length) {
return (standardCapVers[versIndex] & 0xFF) >= ver;
}
}
return false;
}
public int getCapability(int cap) {
int bit = 1 << cap;
if((standardCaps & bit) != 0) {
int versIndex = Integer.bitCount(standardCaps & (bit - 1));
if(versIndex < standardCapVers.length) {
return standardCapVers[versIndex] & 0xFF;
}
}
return -1;
}
public int getExtCapability(EaglercraftUUID uuid) {
if(extendedCaps != null) {
int idx = extendedCaps.indexOf(uuid);
if(idx >= 0) {
return (int) extendedCaps.indexGet(idx) & 0xFF;
}
}
return -1;
}
public static ServerCapabilities getLAN() {
return new ServerCapabilities(VIRTUAL_V3_SERVER_CAPS, VIRTUAL_V3_SERVER_CAPS_VERS, null);
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 lax1dude. All Rights Reserved.
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
@ -14,12 +14,25 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.socket.protocol.handshake;
import net.minecraft.network.PacketBuffer;
public class StandardCaps {
public interface IPluginMessageSendFunction {
public static final int UPDATE = 0;
void sendPluginMessage(String channel, PacketBuffer contents);
public static final int VOICE = 1;
}
public static final int REDIRECT = 2;
public static final int NOTIFICATION = 3;
public static final int PAUSE_MENU = 4;
public static final int WEBVIEW = 5;
public static final int COOKIE = 6;
// reserved
public static final int EAGLER_IP = 7;
}

View File

@ -0,0 +1,160 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayInputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.ReusableByteArrayOutputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleInputBufferImpl;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.util.SimpleOutputBufferImpl;
public class InjectedMessageController extends MessageController {
private static final Logger logger = LogManager.getLogger("InjectedMessageController");
private final ReusableByteArrayInputStream byteInputStreamSingleton = new ReusableByteArrayInputStream();
private final ReusableByteArrayOutputStream byteOutputStreamSingleton = new ReusableByteArrayOutputStream();
private final SimpleInputBufferImpl inputStreamSingleton = new SimpleInputBufferImpl(byteInputStreamSingleton);
private final SimpleOutputBufferImpl outputStreamSingleton = new SimpleOutputBufferImpl(byteOutputStreamSingleton);
public interface IBinarySendFunction {
void sendBinaryFrame(byte[] contents);
}
private final IBinarySendFunction send;
public InjectedMessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction,
IBinarySendFunction send) {
super(protocol, handler, direction);
this.send = send;
}
public boolean handlePacket(byte[] data, int offset) throws IOException {
if(data.length - offset > 1 && data[offset] == (byte) 0xEE) {
GameMessagePacket pkt;
byteInputStreamSingleton.feedBuffer(data, offset);
inputStreamSingleton.readByte();
if(data[offset + 1] == (byte) 0xFF) {
inputStreamSingleton.readByte();
int count = inputStreamSingleton.readVarInt();
for(int i = 0, j, k; i < count; ++i) {
j = inputStreamSingleton.readVarInt();
inputStreamSingleton.setToByteArrayReturns(j - 1);
k = byteInputStreamSingleton.getReaderIndex() + j;
if(j < 0 || j > inputStreamSingleton.available()) {
throw new IOException("Packet fragment is too long: " + j + " > " + inputStreamSingleton.available());
}
pkt = protocol.readPacketV5(receiveDirection, inputStreamSingleton);
if(byteInputStreamSingleton.getReaderIndex() != k) {
throw new IOException("Packet fragment was the wrong length: " + (j + byteInputStreamSingleton.getReaderIndex() - k) + " != " + j);
}
handlePacket(pkt);
}
if(inputStreamSingleton.available() > 0) {
throw new IOException("Leftover data after reading multi-packet! (" + inputStreamSingleton.available() + " bytes)");
}
byteOutputStreamSingleton.feedBuffer(null);
return true;
}
inputStreamSingleton.setToByteArrayReturns(data.length - offset - 2);
pkt = protocol.readPacketV5(receiveDirection, inputStreamSingleton);
if(byteInputStreamSingleton.available() != 0) {
throw new IOException("Packet was the wrong length: " + pkt.getClass().getSimpleName());
}
byteOutputStreamSingleton.feedBuffer(null);
handlePacket(pkt);
return true;
}
return false;
}
@Override
protected void writePacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 2;
byteOutputStreamSingleton.feedBuffer(len == 1 ? new byte[64] : new byte[len]);
byteOutputStreamSingleton.write(0xEE);
protocol.writePacketV5(sendDirection, outputStreamSingleton, packet);
byte[] data = byteOutputStreamSingleton.returnBuffer();
byteOutputStreamSingleton.feedBuffer(null);
if(len != 1 && data.length != len) {
logger.warn("Packet " + packet.getClass().getSimpleName() + " was the wrong length after serialization, "
+ data.length + " != " + len);
}
send.sendBinaryFrame(data);
}
@Override
protected void writeMultiPacket(List<GameMessagePacket> packets) throws IOException {
int total = packets.size();
byte[][] buffer = new byte[total][];
byte[] dat;
for(int i = 0; i < total; ++i) {
GameMessagePacket packet = packets.get(i);
int len = packet.length() + 2;
byteOutputStreamSingleton.feedBuffer(len == 1 ? new byte[64] : new byte[len]);
byteOutputStreamSingleton.write(0xEE);
protocol.writePacketV5(sendDirection, outputStreamSingleton, packet);
dat = byteOutputStreamSingleton.returnBuffer();
byteOutputStreamSingleton.feedBuffer(null);
if(len != 1 && dat.length != len) {
logger.warn("Packet " + packet.getClass().getSimpleName()
+ " was the wrong length after serialization, " + dat.length + " != " + len);
}
buffer[i] = dat;
}
int start = 0;
int i, j, sendCount, totalLen, lastLen;
while(total > start) {
sendCount = 0;
totalLen = 0;
do {
i = buffer[start + sendCount].length - 1;
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
send.sendBinaryFrame(buffer[start++]);
continue;
}
byteOutputStreamSingleton.feedBuffer(new byte[2 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)]);
outputStreamSingleton.writeShort(0xEEFF);
outputStreamSingleton.writeVarInt(sendCount);
for(j = 0; j < sendCount; ++j) {
dat = buffer[start++];
i = dat.length - 1;
outputStreamSingleton.writeVarInt(i);
outputStreamSingleton.write(dat, 1, i);
}
send.sendBinaryFrame(byteOutputStreamSingleton.returnBuffer());
byteOutputStreamSingleton.feedBuffer(null);
}
}
}

View File

@ -0,0 +1,164 @@
/*
* Copyright (c) 2024-2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
import net.minecraft.network.PacketBuffer;
public class LegacyMessageController extends MessageController {
private static final Logger logger = LogManager.getLogger("LegacyMessageController");
public interface IPluginMessageSendFunction {
void sendPluginMessage(String channel, PacketBuffer contents);
}
private final PacketBufferInputWrapper inputStream = new PacketBufferInputWrapper(null);
private final PacketBufferOutputWrapper outputStream = new PacketBufferOutputWrapper(null);
private final IPluginMessageSendFunction send;
public LegacyMessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction,
IPluginMessageSendFunction send) {
super(protocol, handler, direction);
this.send = send;
}
public boolean handlePacket(String channel, PacketBuffer data) throws IOException {
GameMessagePacket pkt;
if(protocol.ver >= 4 && data.readableBytes() > 0 && data.getByte(data.readerIndex()) == (byte) 0xFF
&& channel.equals(GamePluginMessageConstants.V4_CHANNEL)) {
data.readByte();
inputStream.buffer = data;
int count = inputStream.readVarInt();
for(int i = 0, j, k, l; i < count; ++i) {
j = data.readVarIntFromBuffer();
k = data.readerIndex() + j;
l = data.writerIndex();
if(j < 0 || k > l) {
throw new IOException("Packet fragment is too long: " + j + " > " + data.readableBytes());
}
data.writerIndex(k);
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null) {
handlePacket(pkt);
}else {
logger.warn("Could not read packet fragment {} of {}, unknown packet", count, i);
}
if(data.readerIndex() != k) {
logger.warn("Packet fragment {} was the wrong length: {} != {}",
(pkt != null ? pkt.getClass().getSimpleName() : "unknown"), j + data.readerIndex() - k, j);
data.readerIndex(k);
}
data.writerIndex(l);
}
if(data.readableBytes() > 0) {
logger.warn("Leftover data after reading multi-packet! ({} bytes)", data.readableBytes());
}
inputStream.buffer = null;
return true;
}
inputStream.buffer = data;
pkt = protocol.readPacket(channel, receiveDirection, inputStream);
if(pkt != null && inputStream.available() > 0) {
logger.warn("Leftover data after reading packet {}! ({} bytes)", pkt.getClass().getSimpleName(), inputStream.available());
}
inputStream.buffer = null;
if(pkt != null) {
handlePacket(pkt);
return true;
}else {
return false;
}
}
@Override
protected void writePacket(GameMessagePacket packet) throws IOException {
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
String chan = protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && (protocol.ver > 3 || j + 1 != len)) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
send.sendPluginMessage(chan, buf);
}
@Override
protected void writeMultiPacket(List<GameMessagePacket> packets) throws IOException {
int total = packets.size();
PacketBuffer[] toSend = new PacketBuffer[total];
for(int i = 0; i < total; ++i) {
GameMessagePacket packet = packets.get(i);
int len = packet.length() + 1;
PacketBuffer buf = new PacketBuffer(len != 0 ? Unpooled.buffer(len) : Unpooled.buffer(64));
outputStream.buffer = buf;
protocol.writePacket(sendDirection, outputStream, packet);
outputStream.buffer = null;
int j = buf.writerIndex();
if(len != 0 && j != len && (protocol.ver > 3 || j + 1 != len)) {
logger.warn("Packet {} was expected to be {} bytes but was serialized to {} bytes!",
packet.getClass().getSimpleName(), len, j);
}
toSend[i] = buf;
}
int start = 0;
int i, j, sendCount, totalLen, lastLen;
while(total > start) {
sendCount = 0;
totalLen = 0;
do {
i = toSend[start + sendCount].readableBytes();
lastLen = GamePacketOutputBuffer.getVarIntSize(i) + i;
totalLen += lastLen;
++sendCount;
}while(totalLen < 32760 && sendCount < total - start);
if(totalLen >= 32760) {
--sendCount;
totalLen -= lastLen;
}
if(sendCount <= 1) {
send.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, toSend[start++]);
continue;
}
PacketBuffer sendBuffer = new PacketBuffer(
Unpooled.buffer(1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)));
sendBuffer.writerIndex(0);
sendBuffer.writeByte(0xFF);
sendBuffer.writeVarIntToBuffer(sendCount);
for(j = 0; j < sendCount; ++j) {
PacketBuffer dat = toSend[start++];
sendBuffer.writeVarIntToBuffer(dat.readableBytes());
sendBuffer.writeBytes(dat);
}
send.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, sendBuffer);
}
}
}

View File

@ -0,0 +1,101 @@
/*
* Copyright (c) 2025 lax1dude. All Rights Reserved.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageConstants;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePluginMessageProtocol;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessageHandler;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.pkt.GameMessagePacket;
public abstract class MessageController {
private static final Logger logger = LogManager.getLogger("MessageController");
protected final GamePluginMessageProtocol protocol;
protected final GameMessageHandler handler;
protected final int sendDirection;
protected final int receiveDirection;
protected List<GameMessagePacket> sendQueue;
public MessageController(GamePluginMessageProtocol protocol, GameMessageHandler handler, int direction) {
this.protocol = protocol;
this.handler = handler;
this.sendDirection = direction;
this.receiveDirection = direction == GamePluginMessageConstants.CLIENT_TO_SERVER
? GamePluginMessageConstants.SERVER_TO_CLIENT
: GamePluginMessageConstants.CLIENT_TO_SERVER;
this.sendQueue = protocol.ver >= 4 && !EagRuntime.getConfiguration().isEaglerNoDelay()
? new ArrayList<>() : null;
}
public GamePluginMessageProtocol getProtocol() {
return protocol;
}
public boolean isSendQueueEnabled() {
return sendQueue != null;
}
public void sendPacket(GameMessagePacket packet) {
if(sendQueue != null) {
sendQueue.add(packet);
}else {
try {
writePacket(packet);
} catch (IOException ex) {
throw new RuntimeException("Failed to serialize packet: " + packet.getClass().getSimpleName(), ex);
}
}
}
protected abstract void writePacket(GameMessagePacket packet) throws IOException;
public void flush() {
if(sendQueue != null && !sendQueue.isEmpty()) {
try {
writeMultiPacket(sendQueue);
} catch (IOException ex) {
throw new RuntimeException("Failed to serialize packet multi-packet!", ex);
}
if(sendQueue.size() < 64) {
sendQueue.clear();
}else {
sendQueue = new ArrayList<>();
}
}
}
protected abstract void writeMultiPacket(List<GameMessagePacket> packets) throws IOException;
protected void handlePacket(GameMessagePacket packet) {
try {
packet.handlePacket(handler);
}catch(Throwable t) {
logger.error("Failed to handle packet {} in direction {} using handler {}!", packet.getClass().getSimpleName(),
GamePluginMessageConstants.getDirectionString(receiveDirection), handler);
logger.error(t);
}
}
}

View File

@ -14,7 +14,7 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.DataInputStream;
import java.io.EOFException;

View File

@ -14,7 +14,7 @@
*
*/
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
package net.lax1dude.eaglercraft.v1_8.socket.protocol.message;
import java.io.IOException;
import java.io.OutputStream;