mirror of
https://github.com/Eaglercraft-Archive/Eaglercraftx-1.8.8-src.git
synced 2025-06-28 02:48:14 -05:00
Update #51 - Protocol and FPS improvements, better workspace
This commit is contained in:
@ -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'
|
||||
};
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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'
|
||||
};
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
@ -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;
|
Reference in New Issue
Block a user