Update #37 - Touch support without userscript, many other feats

This commit is contained in:
lax1dude
2024-09-21 20:17:42 -07:00
parent 173727c8c4
commit ec1ab8ece3
683 changed files with 62074 additions and 8996 deletions

View File

@ -4,14 +4,22 @@ 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.EagRuntime;
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.PlatformNetworking;
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;
@ -42,20 +50,40 @@ import net.minecraft.util.IChatComponent;
*/
public class ConnectionHandshake {
private static final long baseTimeout = 15000l;
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 boolean attemptHandshake(Minecraft mc, GuiConnecting connecting, GuiScreen ret, String password, boolean allowPlaintext) {
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();
pluginVersion = null;
pluginBrand = null;
protocolVersion = -1;
EaglerOutputStream bao = new EaglerOutputStream();
DataOutputStream d = new DataOutputStream(bao);
@ -63,9 +91,7 @@ public class ConnectionHandshake {
d.writeByte(2); // legacy protocol version
d.writeShort(2); // supported eagler protocols count
d.writeShort(protocolV2); // client supports v2
d.writeShort(protocolV3); // client supports v3
d.write(getSPHandshakeProtocolData()); // write supported eagler protocol versions
d.writeShort(1); // supported game protocols count
d.writeShort(47); // client supports 1.8 protocol
@ -84,9 +110,9 @@ public class ConnectionHandshake {
d.writeByte(username.length());
d.writeBytes(username);
PlatformNetworking.writePlayPacket(bao.toByteArray());
client.send(bao.toByteArray());
byte[] read = awaitNextPacket(baseTimeout);
byte[] read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for server protocol response!");
return false;
@ -115,7 +141,7 @@ public class ConnectionHandshake {
games.append("mc").append(di.readShort());
}
logger.info("Incompatible client: v2 & mc47");
logger.info("Incompatible client: v2/v3/v4 & mc47");
logger.info("Server supports: {}", protocols);
logger.info("Server supports: {}", games);
@ -128,11 +154,11 @@ public class ConnectionHandshake {
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_VERSION) {
int serverVers = di.readShort();
protocolVersion = di.readShort();
if(serverVers != protocolV2 && serverVers != protocolV3) {
logger.info("Incompatible server version: {}", serverVers);
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", new ChatComponentText(serverVers < protocolV2 ? "Outdated Server" : "Outdated Client")));
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;
}
@ -143,7 +169,7 @@ public class ConnectionHandshake {
return false;
}
logger.info("Server protocol: {}", serverVers);
logger.info("Server protocol: {}", protocolVersion);
int msgLen = di.read();
byte[] dat = new byte[msgLen];
@ -260,10 +286,19 @@ public class ConnectionHandshake {
}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);
}
}
PlatformNetworking.writePlayPacket(bao.toByteArray());
client.send(bao.toByteArray());
read = awaitNextPacket(baseTimeout);
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login negotiation response!");
return false;
@ -280,52 +315,79 @@ public class ConnectionHandshake {
Minecraft.getMinecraft().getSession().update(serverUsername, new EaglercraftUUID(di.readLong(), di.readLong()));
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
String profileDataType = "skin_v1";
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] packetSkin = EaglerProfile.getSkinPacket();
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);
}
d.writeShort(packetSkin.length);
d.write(packetSkin);
PlatformNetworking.writePlayPacket(bao.toByteArray());
profileDataToSend.put(protocolVersion >= 4 ? "skin_v2" : "skin_v1", packetSkin);
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
profileDataType = "cape_v1";
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
byte[] packetCape = EaglerProfile.getCapePacket();
if(packetCape.length > 0xFFFF) {
throw new IOException("Cape packet is too long: " + packetCape.length);
}
d.writeShort(packetCape.length);
d.write(packetCape);
PlatformNetworking.writePlayPacket(bao.toByteArray());
profileDataToSend.put("cape_v1", packetCape);
byte[] packetSignatureData = UpdateService.getClientSignatureData();
if(packetSignatureData != null) {
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_PROFILE_DATA);
profileDataType = "update_cert_v1";
d.writeByte(profileDataType.length());
d.writeBytes(profileDataType);
if(packetSignatureData.length > 0xFFFF) {
throw new IOException("Update certificate login packet is too long: " + packetSignatureData.length);
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());
}
d.writeShort(packetSignatureData.length);
d.write(packetSignatureData);
PlatformNetworking.writePlayPacket(bao.toByteArray());
}
bao.reset();
d.writeByte(HandshakePacketTypes.PROTOCOL_CLIENT_FINISH_LOGIN);
PlatformNetworking.writePlayPacket(bao.toByteArray());
client.send(bao.toByteArray());
read = awaitNextPacket(baseTimeout);
read = awaitNextPacket(client, baseTimeout);
if(read == null) {
logger.error("Read timed out while waiting for login confirmation response!");
return false;
@ -336,13 +398,13 @@ public class ConnectionHandshake {
if(type == HandshakePacketTypes.PROTOCOL_SERVER_FINISH_LOGIN) {
return true;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, connecting, ret, di, serverVers == protocolV2);
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_DENY_LOGIN) {
if(serverVers == protocolV2) {
if(protocolVersion == protocolV2) {
msgLen = di.read();
}else {
msgLen = di.readUnsignedShort();
@ -353,13 +415,13 @@ public class ConnectionHandshake {
mc.displayGuiScreen(new GuiDisconnected(ret, "connect.failed", IChatComponent.Serializer.jsonToComponent(errStr)));
return false;
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, connecting, ret, di, serverVers == protocolV2);
showError(mc, client, connecting, ret, di, protocolVersion == protocolV2);
return false;
}else {
return false;
}
}else if(type == HandshakePacketTypes.PROTOCOL_SERVER_ERROR) {
showError(mc, connecting, ret, di, true);
showError(mc, client, connecting, ret, di, true);
return false;
}else {
return false;
@ -372,37 +434,51 @@ public class ConnectionHandshake {
}
private static byte[] awaitNextPacket(long timeout) {
long millis = System.currentTimeMillis();
byte[] b;
while((b = PlatformNetworking.readPlayPacket()) == null) {
if(PlatformNetworking.playConnectionState().isClosed()) {
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;
}
try {
Thread.sleep(50l);
} catch (InterruptedException e) {
}
if(System.currentTimeMillis() - millis > timeout) {
PlatformNetworking.playDisconnect();
if(EagRuntime.steadyTimeMillis() - millis > timeout) {
client.close();
return null;
}
}
return b;
return b.getByteArray();
}
private static void showError(Minecraft mc, GuiConnecting connecting, GuiScreen scr, DataInputStream err, boolean v2) throws IOException {
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(PlatformNetworking.getCurrentURI());
RateLimitTracker.registerBlock(client.getCurrentURI());
mc.displayGuiScreen(GuiDisconnected.createRateLimitKick(scr));
}else if(errorCode == HandshakePacketTypes.SERVER_ERROR_RATELIMIT_LOCKED) {
RateLimitTracker.registerLockOut(PlatformNetworking.getCurrentURI());
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)));

View File

@ -1,24 +1,19 @@
package net.lax1dude.eaglercraft.v1_8.socket;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformNetworking;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
import net.lax1dude.eaglercraft.v1_8.netty.ByteBuf;
import net.lax1dude.eaglercraft.v1_8.netty.Unpooled;
import net.minecraft.network.EnumConnectionState;
import net.minecraft.network.EnumPacketDirection;
import net.minecraft.network.INetHandler;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.IChatComponent;
/**
* Copyright (c) 2022-2024 lax1dude, ayunami2000. All Rights Reserved.
* Copyright (c) 2022-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
@ -32,7 +27,7 @@ import net.minecraft.util.IChatComponent;
* POSSIBILITY OF SUCH DAMAGE.
*
*/
public class EaglercraftNetworkManager {
public abstract class EaglercraftNetworkManager {
protected final String address;
protected INetHandler nethandler = null;
@ -63,103 +58,23 @@ public class EaglercraftNetworkManager {
return pluginVersion;
}
public void connect() {
PlatformNetworking.startPlayConnection(address);
public abstract void connect();
public abstract EnumEaglerConnectionState getConnectStatus();
public String getAddress() {
return address;
}
public EnumEaglerConnectionState getConnectStatus() {
return PlatformNetworking.playConnectionState();
}
public void closeChannel(IChatComponent reason) {
PlatformNetworking.playDisconnect();
if(nethandler != null) {
nethandler.onDisconnect(reason);
}
clientDisconnected = true;
}
public abstract void closeChannel(IChatComponent reason);
public void setConnectionState(EnumConnectionState state) {
packetState = state;
}
public void processReceivedPackets() throws IOException {
if(nethandler == null) return;
List<byte[]> pkts = PlatformNetworking.readAllPacket();
public abstract void processReceivedPackets() throws IOException;
if(pkts == null) {
return;
}
for(int i = 0, l = pkts.size(); i < l; ++i) {
byte[] next = pkts.get(i);
++debugPacketCounter;
try {
ByteBuf nettyBuffer = Unpooled.buffer(next, next.length);
nettyBuffer.writerIndex(next.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
int pktId = input.readVarIntFromBuffer();
Packet pkt;
try {
pkt = packetState.getPacket(EnumPacketDirection.CLIENTBOUND, pktId);
}catch(IllegalAccessException | InstantiationException ex) {
throw new IOException("Recieved a packet with type " + pktId + " which is invalid!");
}
if(pkt == null) {
throw new IOException("Recieved packet type " + pktId + " which is undefined in state " + packetState);
}
try {
pkt.readPacketData(input);
}catch(Throwable t) {
throw new IOException("Failed to read packet type '" + pkt.getClass().getSimpleName() + "'", t);
}
try {
pkt.processPacket(nethandler);
}catch(Throwable t) {
logger.error("Failed to process {}! It'll be skipped for debug purposes.", pkt.getClass().getSimpleName());
logger.error(t);
}
}catch(Throwable t) {
logger.error("Failed to process websocket frame {}! It'll be skipped for debug purposes.", debugPacketCounter);
logger.error(t);
}
}
}
public void sendPacket(Packet pkt) {
if(!isChannelOpen()) {
logger.error("Packet was sent on a closed connection: {}", pkt.getClass().getSimpleName());
return;
}
int i;
try {
i = packetState.getPacketId(EnumPacketDirection.SERVERBOUND, pkt);
}catch(Throwable t) {
logger.error("Incorrect packet for state: {}", pkt.getClass().getSimpleName());
return;
}
temporaryBuffer.clear();
temporaryBuffer.writeVarIntToBuffer(i);
try {
pkt.writePacketData(temporaryBuffer);
}catch(IOException ex) {
logger.error("Failed to write packet {}!", pkt.getClass().getSimpleName());
return;
}
int len = temporaryBuffer.writerIndex();
byte[] bytes = new byte[len];
temporaryBuffer.getBytes(0, bytes);
PlatformNetworking.writePlayPacket(bytes);
}
public abstract void sendPacket(Packet pkt);
public void setNetHandler(INetHandler nethandler) {
this.nethandler = nethandler;
@ -181,18 +96,7 @@ public class EaglercraftNetworkManager {
throw new CompressionNotSupportedException();
}
public boolean checkDisconnected() {
if(PlatformNetworking.playConnectionState().isClosed()) {
try {
processReceivedPackets(); // catch kick message
} catch (IOException e) {
}
doClientDisconnect(new ChatComponentTranslation("disconnect.endOfStream"));
return true;
}else {
return false;
}
}
public abstract boolean checkDisconnected();
protected boolean clientDisconnected = false;

View File

@ -46,7 +46,7 @@ public class GuiHandshakeApprove extends GuiScreen {
public void initGui() {
this.buttonList.clear();
titleString = I18n.format("handshakeApprove." + message + ".title");
bodyLines = new ArrayList();
bodyLines = new ArrayList<>();
int i = 0;
boolean wasNull = true;
while(true) {

View File

@ -4,6 +4,8 @@ import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import net.lax1dude.eaglercraft.v1_8.EagRuntime;
/**
* Copyright (c) 2022-2023 lax1dude, ayunami2000. All Rights Reserved.
*
@ -23,12 +25,12 @@ public class RateLimitTracker {
private static long lastTickUpdate = 0l;
private static final Map<String, Long> blocks = new HashMap();
private static final Map<String, Long> lockout = new HashMap();
private static final Map<String, Long> blocks = new HashMap<>();
private static final Map<String, Long> lockout = new HashMap<>();
public static boolean isLockedOut(String addr) {
Long lockoutStatus = lockout.get(addr);
return lockoutStatus != null && System.currentTimeMillis() - lockoutStatus.longValue() < 300000l;
return lockoutStatus != null && EagRuntime.steadyTimeMillis() - lockoutStatus.longValue() < 300000l;
}
public static boolean isProbablyLockedOut(String addr) {
@ -36,17 +38,17 @@ public class RateLimitTracker {
}
public static void registerBlock(String addr) {
blocks.put(addr, System.currentTimeMillis());
blocks.put(addr, EagRuntime.steadyTimeMillis());
}
public static void registerLockOut(String addr) {
long millis = System.currentTimeMillis();
long millis = EagRuntime.steadyTimeMillis();
blocks.put(addr, millis);
lockout.put(addr, millis);
}
public static void tick() {
long millis = System.currentTimeMillis();
long millis = EagRuntime.steadyTimeMillis();
if(millis - lastTickUpdate > 5000l) {
lastTickUpdate = millis;
Iterator<Long> blocksItr = blocks.values().iterator();

View File

@ -1,6 +1,7 @@
package net.lax1dude.eaglercraft.v1_8.socket;
import net.lax1dude.eaglercraft.v1_8.internal.IServerQuery;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformNetworking;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
@ -26,7 +27,8 @@ public class ServerQueryDispatch {
public static IServerQuery sendServerQuery(String uri, String accept) {
logger.info("Sending {} query to: \"{}\"", accept, uri);
return PlatformNetworking.sendServerQuery(uri, accept);
IWebSocketClient sockClient = PlatformNetworking.openWebSocket(uri);
return sockClient != null ? new ServerQueryImpl(sockClient, accept) : null;
}
}

View File

@ -0,0 +1,179 @@
package net.lax1dude.eaglercraft.v1_8.socket;
import java.util.LinkedList;
import java.util.List;
import org.json.JSONObject;
import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
import net.lax1dude.eaglercraft.v1_8.internal.EnumServerRateLimit;
import net.lax1dude.eaglercraft.v1_8.internal.IServerQuery;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketFrame;
import net.lax1dude.eaglercraft.v1_8.internal.PlatformRuntime;
import net.lax1dude.eaglercraft.v1_8.internal.QueryResponse;
import net.lax1dude.eaglercraft.v1_8.log4j.LogManager;
import net.lax1dude.eaglercraft.v1_8.log4j.Logger;
/**
* Copyright (c) 2022-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.
*
*/
class ServerQueryImpl implements IServerQuery {
public static final Logger logger = LogManager.getLogger("WebSocketQuery");
private final List<QueryResponse> queryResponses = new LinkedList<>();
private final List<byte[]> queryResponsesBytes = new LinkedList<>();
protected final IWebSocketClient websocketClient;
protected final String uri;
protected final String accept;
protected boolean hasSentAccept = false;
protected boolean open = true;
protected boolean alive = false;
protected long pingStart = -1l;
protected long pingTimer = -1l;
private EnumServerRateLimit rateLimit = EnumServerRateLimit.OK;
ServerQueryImpl(IWebSocketClient websocketClient, String accept) {
this.websocketClient = websocketClient;
this.uri = websocketClient.getCurrentURI();
this.accept = accept;
}
@Override
public void update() {
if(!hasSentAccept && websocketClient.getState() == EnumEaglerConnectionState.CONNECTED) {
hasSentAccept = true;
websocketClient.send("Accept: " + accept);
}
List<IWebSocketFrame> lst = websocketClient.getNextFrames();
if(lst != null) {
for(int i = 0, l = lst.size(); i < l; ++i) {
IWebSocketFrame frame = lst.get(i);
alive = true;
if(pingTimer == -1) {
pingTimer = PlatformRuntime.steadyTimeMillis() - pingStart;
if(pingTimer < 1) {
pingTimer = 1;
}
}
if(frame.isString()) {
String str = frame.getString();
if(str.equalsIgnoreCase("BLOCKED")) {
logger.error("Reached full IP ratelimit for {}!", uri);
rateLimit = EnumServerRateLimit.BLOCKED;
return;
}
if(str.equalsIgnoreCase("LOCKED")) {
logger.error("Reached full IP ratelimit lockout for {}!", uri);
rateLimit = EnumServerRateLimit.LOCKED_OUT;
return;
}
try {
JSONObject obj = new JSONObject(str);
if("blocked".equalsIgnoreCase(obj.optString("type", null))) {
logger.error("Reached query ratelimit for {}!", uri);
rateLimit = EnumServerRateLimit.BLOCKED;
}else if("locked".equalsIgnoreCase(obj.optString("type", null))) {
logger.error("Reached query ratelimit lockout for {}!", uri);
rateLimit = EnumServerRateLimit.LOCKED_OUT;
}else {
queryResponses.add(new QueryResponse(obj, pingTimer));
}
}catch(Throwable t) {
logger.error("Exception thrown parsing websocket query response from \"" + uri + "\"!");
logger.error(t);
}
}else {
queryResponsesBytes.add(frame.getByteArray());
}
}
}
if(websocketClient.isClosed()) {
open = false;
}
}
@Override
public void send(String str) {
if(websocketClient.getState() == EnumEaglerConnectionState.CONNECTED) {
websocketClient.send(str);
}
}
@Override
public void send(byte[] bytes) {
if(websocketClient.getState() == EnumEaglerConnectionState.CONNECTED) {
websocketClient.send(bytes);
}
}
@Override
public int responsesAvailable() {
synchronized(queryResponses) {
return queryResponses.size();
}
}
@Override
public QueryResponse getResponse() {
synchronized(queryResponses) {
if(queryResponses.size() > 0) {
return queryResponses.remove(0);
}else {
return null;
}
}
}
@Override
public int binaryResponsesAvailable() {
synchronized(queryResponsesBytes) {
return queryResponsesBytes.size();
}
}
@Override
public byte[] getBinaryResponse() {
synchronized(queryResponsesBytes) {
if(queryResponsesBytes.size() > 0) {
return queryResponsesBytes.remove(0);
}else {
return null;
}
}
}
@Override
public QueryReadyState readyState() {
return open ? (alive ? QueryReadyState.OPEN : QueryReadyState.CONNECTING)
: (alive ? QueryReadyState.CLOSED : QueryReadyState.FAILED);
}
@Override
public void close() {
if(open) {
open = false;
websocketClient.close();
}
}
@Override
public EnumServerRateLimit getRateLimit() {
return rateLimit;
}
}

View File

@ -0,0 +1,152 @@
package net.lax1dude.eaglercraft.v1_8.socket;
import java.io.IOException;
import java.util.List;
import net.lax1dude.eaglercraft.v1_8.internal.EnumEaglerConnectionState;
import net.lax1dude.eaglercraft.v1_8.internal.IWebSocketClient;
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.minecraft.network.EnumPacketDirection;
import net.minecraft.network.Packet;
import net.minecraft.network.PacketBuffer;
import net.minecraft.util.ChatComponentTranslation;
import net.minecraft.util.IChatComponent;
/**
* Copyright (c) 2022-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.
*
*/
public class WebSocketNetworkManager extends EaglercraftNetworkManager {
protected final IWebSocketClient webSocketClient;
public WebSocketNetworkManager(IWebSocketClient webSocketClient) {
super(webSocketClient.getCurrentURI());
this.webSocketClient = webSocketClient;
}
public void connect() {
}
public EnumEaglerConnectionState getConnectStatus() {
return webSocketClient.getState();
}
public void closeChannel(IChatComponent reason) {
webSocketClient.close();
if(nethandler != null) {
nethandler.onDisconnect(reason);
}
clientDisconnected = true;
}
public void processReceivedPackets() throws IOException {
if(nethandler == null) return;
if(webSocketClient.availableStringFrames() > 0) {
logger.warn("discarding {} string frames recieved on a binary connection", webSocketClient.availableStringFrames());
webSocketClient.clearStringFrames();
}
List<IWebSocketFrame> pkts = webSocketClient.getNextBinaryFrames();
if(pkts == null) {
return;
}
for(int i = 0, l = pkts.size(); i < l; ++i) {
IWebSocketFrame next = pkts.get(i);
++debugPacketCounter;
try {
byte[] asByteArray = next.getByteArray();
ByteBuf nettyBuffer = Unpooled.buffer(asByteArray, asByteArray.length);
nettyBuffer.writerIndex(asByteArray.length);
PacketBuffer input = new PacketBuffer(nettyBuffer);
int pktId = input.readVarIntFromBuffer();
Packet pkt;
try {
pkt = packetState.getPacket(EnumPacketDirection.CLIENTBOUND, pktId);
}catch(IllegalAccessException | InstantiationException ex) {
throw new IOException("Recieved a packet with type " + pktId + " which is invalid!");
}
if(pkt == null) {
throw new IOException("Recieved packet type " + pktId + " which is undefined in state " + packetState);
}
try {
pkt.readPacketData(input);
}catch(Throwable t) {
throw new IOException("Failed to read packet type '" + pkt.getClass().getSimpleName() + "'", t);
}
try {
pkt.processPacket(nethandler);
}catch(Throwable t) {
logger.error("Failed to process {}! It'll be skipped for debug purposes.", pkt.getClass().getSimpleName());
logger.error(t);
}
}catch(Throwable t) {
logger.error("Failed to process websocket frame {}! It'll be skipped for debug purposes.", debugPacketCounter);
logger.error(t);
}
}
}
public void sendPacket(Packet pkt) {
if(!isChannelOpen()) {
logger.error("Packet was sent on a closed connection: {}", pkt.getClass().getSimpleName());
return;
}
int i;
try {
i = packetState.getPacketId(EnumPacketDirection.SERVERBOUND, pkt);
}catch(Throwable t) {
logger.error("Incorrect packet for state: {}", pkt.getClass().getSimpleName());
return;
}
temporaryBuffer.clear();
temporaryBuffer.writeVarIntToBuffer(i);
try {
pkt.writePacketData(temporaryBuffer);
}catch(IOException ex) {
logger.error("Failed to write packet {}!", pkt.getClass().getSimpleName());
return;
}
int len = temporaryBuffer.writerIndex();
byte[] bytes = new byte[len];
temporaryBuffer.getBytes(0, bytes);
webSocketClient.send(bytes);
}
public boolean checkDisconnected() {
if(webSocketClient.isClosed()) {
try {
processReceivedPackets(); // catch kick message
} catch (IOException e) {
}
doClientDisconnect(new ChatComponentTranslation("disconnect.endOfStream"));
return true;
}else {
return false;
}
}
}

View File

@ -0,0 +1,130 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
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;
/**
* 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.
*
*/
public class ClientV3MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public ClientV3MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = netHandler;
}
public void handleServer(SPacketEnableFNAWSkinsEAG packet) {
netHandler.currentFNAWSkinAllowedState = packet.enableSkins;
netHandler.currentFNAWSkinForcedState = packet.enableSkins;
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) {
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);
}
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) {
if (VoiceClientController.isClientSupported()) {
if (packet.isAnnounceType) {
VoiceClientController.handleVoiceSignalPacketTypeConnectAnnounce(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
} else {
VoiceClientController.handleVoiceSignalPacketTypeConnect(
new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.offer);
}
}
}
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));
}
}
}

View File

@ -0,0 +1,222 @@
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.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;
/**
* 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.
*
*/
public class ClientV4MessageHandler implements GameMessageHandler {
private final NetHandlerPlayClient netHandler;
public ClientV4MessageHandler(NetHandlerPlayClient netHandler) {
this.netHandler = 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(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);
}
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(SPacketVoiceSignalConnectV4EAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeConnect(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast), packet.offer);
}
}
public void handleServer(SPacketVoiceSignalConnectAnnounceV4EAG packet) {
if (VoiceClientController.isClientSupported()) {
VoiceClientController.handleVoiceSignalPacketTypeConnectAnnounce(new EaglercraftUUID(packet.uuidMost, packet.uuidLeast));
}
}
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);
}
public void handleServer(SPacketForceClientSkinCustomV4EAG packet) {
EaglerProfile.handleForceSkinCustom(packet.modelID, SkinPacketVersionCache.convertToV3Raw(packet.customSkin));
}
public void handleServer(SPacketSetServerCookieV4EAG packet) {
if(!netHandler.isClientInEaglerSingleplayerOrLAN() && Minecraft.getMinecraft().getCurrentServerData().enableCookies) {
ServerCookieDataStore.saveCookie(netHandler.getNetworkManager().getAddress(), packet.expires, packet.data,
packet.revokeQuerySupported, packet.saveCookieToDisk);
}
}
public void handleServer(SPacketRedirectClientV4EAG packet) {
Minecraft.getMinecraft().handleReconnectPacket(packet.redirectURI);
}
public void handleServer(SPacketOtherPlayerClientUUIDV4EAG packet) {
ClientUUIDLoadingCache.handleResponse(packet.requestId, new EaglercraftUUID(packet.clientUUIDMost, packet.clientUUIDLeast));
}
public void handleServer(SPacketForceClientCapePresetV4EAG packet) {
EaglerProfile.handleForceCapePreset(packet.presetCape);
}
public void handleServer(SPacketForceClientCapeCustomV4EAG packet) {
EaglerProfile.handleForceCapeCustom(packet.customCape);
}
public void handleServer(SPacketInvalidatePlayerCacheV4EAG packet) {
if(packet.players != null && packet.players.size() > 0) {
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);
}
}
}
}
public void handleServer(SPacketUnforceClientV4EAG packet) {
if(packet.resetSkin) {
EaglerProfile.isServerSkinOverride = false;
}
if(packet.resetCape) {
EaglerProfile.isServerCapeOverride = false;
}
if(packet.resetFNAW) {
netHandler.currentFNAWSkinForcedState = false;
Minecraft.getMinecraft().getRenderManager().setEnableFNAWSkins(
netHandler.currentFNAWSkinAllowedState && Minecraft.getMinecraft().gameSettings.enableFNAWSkins);
}
}
public void handleServer(SPacketCustomizePauseMenuV4EAG packet) {
PauseMenuCustomizeState.loadPacket(packet);
}
public void handleServer(SPacketServerInfoDataChunkV4EAG packet) {
ServerInfoCache.handleChunk(packet);
}
public void handleServer(SPacketWebViewMessageV4EAG packet) {
WebViewOverlayController.handleMessagePacket(packet);
}
public void handleServer(SPacketNotifIconsRegisterV4EAG packet) {
netHandler.getNotifManager().processPacketAddIcons(packet);
}
public void handleServer(SPacketNotifIconsReleaseV4EAG packet) {
netHandler.getNotifManager().processPacketRemIcons(packet);
}
public void handleServer(SPacketNotifBadgeShowV4EAG packet) {
netHandler.getNotifManager().processPacketShowBadge(packet);
}
public void handleServer(SPacketNotifBadgeHideV4EAG packet) {
netHandler.getNotifManager().processPacketHideBadge(packet);
}
}

View File

@ -0,0 +1,199 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.io.IOException;
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;
/**
* 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.
*
*/
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 = 0, totalLen = 0;
PacketBuffer sendBuffer;
while(sendQueueV4.size() > 0) {
do {
i = sendQueueV4.get(sendCount++).readableBytes();
totalLen += GamePacketOutputBuffer.getVarIntSize(i) + i;
}while(totalLen < 32760 && sendCount < sendQueueV4.size());
if(totalLen >= 32760) {
--sendCount;
}
if(sendCount <= 1) {
pkt = sendQueueV4.remove(0);
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, pkt);
continue;
}
sendBuffer = new PacketBuffer(Unpooled.buffer(1 + totalLen + GamePacketOutputBuffer.getVarIntSize(sendCount)));
sendBuffer.writeByte(0xFF);
sendBuffer.writeVarIntToBuffer(sendCount);
for(j = 0; j < sendCount; ++j) {
pkt = sendQueueV4.remove(0);
sendBuffer.writeVarIntToBuffer(pkt.readableBytes());
sendBuffer.writeBytes(pkt);
}
sendFunction.sendPluginMessage(GamePluginMessageConstants.V4_CHANNEL, sendBuffer);
}
}
}
}
public static GameMessageHandler createClientHandler(int protocolVersion, NetHandlerPlayClient netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ClientV3MessageHandler(netHandler);
case 4:
return new ClientV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
public static GameMessageHandler createServerHandler(int protocolVersion, NetHandlerPlayServer netHandler) {
switch(protocolVersion) {
case 2:
case 3:
return new ServerV3MessageHandler(netHandler);
case 4:
return new ServerV4MessageHandler(netHandler);
default:
throw new IllegalArgumentException("Unknown protocol verison: " + protocolVersion);
}
}
}

View File

@ -0,0 +1,24 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import net.minecraft.network.PacketBuffer;
/**
* 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.
*
*/
public interface IPluginMessageSendFunction {
void sendPluginMessage(String channel, PacketBuffer contents);
}

View File

@ -0,0 +1,303 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InputStream;
import net.lax1dude.eaglercraft.v1_8.DecoderException;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketInputBuffer;
import net.minecraft.network.PacketBuffer;
/**
* 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.
*
*/
public class PacketBufferInputWrapper implements GamePacketInputBuffer {
protected PacketBuffer buffer;
public PacketBufferInputWrapper(PacketBuffer buffer) {
this.buffer = buffer;
}
public PacketBuffer getBuffer() {
return buffer;
}
public void setBuffer(PacketBuffer buffer) {
this.buffer = buffer;
}
@Override
public void readFully(byte[] b) throws IOException {
try {
buffer.readBytes(b);
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public void readFully(byte[] b, int off, int len) throws IOException {
try {
buffer.readBytes(b, off, len);
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public int skipBytes(int n) throws IOException {
int r = buffer.readableBytes();
if(n > r) {
n = r;
}
buffer.readerIndex(buffer.readerIndex() + n);
return n;
}
@Override
public boolean readBoolean() throws IOException {
try {
return buffer.readBoolean();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public byte readByte() throws IOException {
try {
return buffer.readByte();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public int readUnsignedByte() throws IOException {
try {
return buffer.readUnsignedByte();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public short readShort() throws IOException {
try {
return buffer.readShort();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public int readUnsignedShort() throws IOException {
try {
return buffer.readUnsignedShort();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public char readChar() throws IOException {
try {
return buffer.readChar();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public int readInt() throws IOException {
try {
return buffer.readInt();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public long readLong() throws IOException {
try {
return buffer.readLong();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public float readFloat() throws IOException {
try {
return buffer.readFloat();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public double readDouble() throws IOException {
try {
return buffer.readDouble();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public String readLine() throws IOException {
throw new UnsupportedOperationException();
}
@Override
public String readUTF() throws IOException {
return DataInputStream.readUTF(this);
}
@Override
public void skipAllBytes(int n) throws IOException {
if(buffer.readableBytes() < n) {
throw new EOFException();
}
buffer.readerIndex(buffer.readerIndex() + n);
}
@Override
public int readVarInt() throws IOException {
try {
return buffer.readVarIntFromBuffer();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public long readVarLong() throws IOException {
try {
return buffer.readVarLong();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public String readStringMC(int maxLen) throws IOException {
try {
return buffer.readStringFromBuffer(maxLen);
}catch(DecoderException ex) {
throw new IOException(ex.getMessage());
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public String readStringEaglerASCII8() throws IOException {
int len = readUnsignedByte();
char[] ret = new char[len];
for(int i = 0; i < len; ++i) {
ret[i] = (char)readByte();
}
return new String(ret);
}
@Override
public String readStringEaglerASCII16() throws IOException {
int len = readUnsignedShort();
char[] ret = new char[len];
for(int i = 0; i < len; ++i) {
ret[i] = (char)readByte();
}
return new String(ret);
}
@Override
public byte[] readByteArrayMC() throws IOException {
try {
return buffer.readByteArray();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
@Override
public int available() throws IOException {
return buffer.readableBytes();
}
@Override
public InputStream stream() {
return new InputStream() {
@Override
public int read() throws IOException {
if(buffer.readableBytes() > 0) {
return buffer.readUnsignedShort();
}else {
return -1;
}
}
@Override
public int read(byte b[], int off, int len) throws IOException {
int avail = buffer.readableBytes();
if(avail == 0) return -1;
len = avail > len ? avail : len;
buffer.readBytes(b, off, len);
return len;
}
@Override
public long skip(long n) throws IOException {
return PacketBufferInputWrapper.this.skipBytes((int)n);
}
@Override
public int available() throws IOException {
return buffer.readableBytes();
}
@Override
public boolean markSupported() {
return true;
}
@Override
public synchronized void mark(int readlimit) {
buffer.markReaderIndex();
}
@Override
public synchronized void reset() throws IOException {
try {
buffer.resetReaderIndex();
}catch(IndexOutOfBoundsException ex) {
throw new EOFException();
}
}
};
}
@Override
public byte[] toByteArray() throws IOException {
byte[] ret = new byte[buffer.readableBytes()];
buffer.readBytes(ret);
return ret;
}
}

View File

@ -0,0 +1,316 @@
package net.lax1dude.eaglercraft.v1_8.socket.protocol.client;
import java.io.IOException;
import java.io.OutputStream;
import net.lax1dude.eaglercraft.v1_8.socket.protocol.GamePacketOutputBuffer;
import net.minecraft.network.PacketBuffer;
/**
* 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.
*
*/
public class PacketBufferOutputWrapper implements GamePacketOutputBuffer {
protected PacketBuffer buffer;
public PacketBufferOutputWrapper(PacketBuffer buffer) {
this.buffer = buffer;
}
public PacketBuffer getBuffer() {
return buffer;
}
public void setBuffer(PacketBuffer buffer) {
this.buffer = buffer;
}
@Override
public void write(int b) throws IOException {
try {
buffer.writeByte(b);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void write(byte[] b) throws IOException {
try {
buffer.writeBytes(b);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void write(byte[] b, int off, int len) throws IOException {
try {
buffer.writeBytes(b, off, len);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeBoolean(boolean v) throws IOException {
try {
buffer.writeBoolean(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeByte(int v) throws IOException {
try {
buffer.writeByte(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeShort(int v) throws IOException {
try {
buffer.writeShort(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeChar(int v) throws IOException {
try {
buffer.writeChar(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeInt(int v) throws IOException {
try {
buffer.writeInt(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeLong(long v) throws IOException {
try {
buffer.writeLong(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeFloat(float v) throws IOException {
try {
buffer.writeFloat(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeDouble(double v) throws IOException {
try {
buffer.writeDouble(v);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeBytes(String s) throws IOException {
try {
int l = s.length();
for(int i = 0; i < l; ++i) {
buffer.writeByte((int)s.charAt(i));
}
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeChars(String s) throws IOException {
try {
int l = s.length();
for(int i = 0; i < l; ++i) {
buffer.writeChar(s.charAt(i));
}
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public final void writeUTF(String str) throws IOException {
long utfCount = countUTFBytes(str);
if (utfCount > 65535) {
throw new IOException("String is longer than 65535 bytes when encoded as UTF8!");
}
byte[] arr = new byte[(int) utfCount + 2];
int offset = 2;
arr[0] = (byte)(((int)utfCount >>> 8) & 0xFF);
arr[1] = (byte)((int)utfCount & 0xFF);
offset = writeUTFBytesToBuffer(str, arr, offset);
try {
buffer.writeBytes(arr, 0, offset);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
private static long countUTFBytes(String str) {
int utfCount = 0;
int length = str.length();
for (int i = 0; i < length; i++) {
int charValue = str.charAt(i);
if (charValue > 0 && charValue <= 127) {
utfCount++;
} else if (charValue <= 2047) {
utfCount += 2;
} else {
utfCount += 3;
}
}
return utfCount;
}
private static int writeUTFBytesToBuffer(String str, byte[] buffer, int offset) throws IOException {
int length = str.length();
for (int i = 0; i < length; i++) {
int charValue = str.charAt(i);
if (charValue > 0 && charValue <= 127) {
buffer[offset++] = (byte) charValue;
} else if (charValue <= 2047) {
buffer[offset++] = (byte) (0xc0 | (0x1f & (charValue >> 6)));
buffer[offset++] = (byte) (0x80 | (0x3f & charValue));
} else {
buffer[offset++] = (byte) (0xe0 | (0x0f & (charValue >> 12)));
buffer[offset++] = (byte) (0x80 | (0x3f & (charValue >> 6)));
buffer[offset++] = (byte) (0x80 | (0x3f & charValue));
}
}
return offset;
}
@Override
public void writeVarInt(int i) throws IOException {
try {
buffer.writeVarIntToBuffer(i);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeVarLong(long i) throws IOException {
try {
buffer.writeVarLong(i);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeStringMC(String str) throws IOException {
try {
buffer.writeString(str);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeStringEaglerASCII8(String str) throws IOException {
int len = str.length();
if(len > 255) {
throw new IOException("String is longer than 255 chars! (" + len + ")");
}
try {
buffer.writeByte(len);
for(int i = 0, j; i < len; ++i) {
j = (int)str.charAt(i);
if(j > 255) {
j = (int)'?';
}
buffer.writeByte(j);
}
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeStringEaglerASCII16(String str) throws IOException {
int len = str.length();
if(len > 65535) {
throw new IOException("String is longer than 65535 chars! (" + len + ")");
}
try {
buffer.writeShort(len);
for(int i = 0, j; i < len; ++i) {
j = (int)str.charAt(i);
if(j > 255) {
j = (int)'?';
}
buffer.writeByte(j);
}
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void writeByteArrayMC(byte[] bytes) throws IOException {
try {
buffer.writeByteArray(bytes);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public OutputStream stream() {
return new OutputStream() {
@Override
public void write(int b) throws IOException {
try {
buffer.writeByte(b);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
@Override
public void write(byte b[], int off, int len) throws IOException {
try {
buffer.writeBytes(b, off, len);
}catch(IndexOutOfBoundsException ex) {
throw new IOException("Packet buffer overflowed!");
}
}
};
}
}