From 68a3000498316d77f3456e4880731cb519672b7d Mon Sep 17 00:00:00 2001 From: Rainnny7 Date: Sat, 6 Apr 2024 19:17:56 -0400 Subject: [PATCH] Add MC packets --- .../java/me/braydon/mc/common/EnumUtils.java | 26 ++++++++ .../mc/common/packet/MinecraftPacket.java | 65 +++++++++++++++++++ .../impl/PacketHandshakingInSetProtocol.java | 64 ++++++++++++++++++ .../packet/impl/PacketStatusInStart.java | 62 ++++++++++++++++++ 4 files changed, 217 insertions(+) create mode 100644 src/main/java/me/braydon/mc/common/EnumUtils.java create mode 100644 src/main/java/me/braydon/mc/common/packet/MinecraftPacket.java create mode 100644 src/main/java/me/braydon/mc/common/packet/impl/PacketHandshakingInSetProtocol.java create mode 100644 src/main/java/me/braydon/mc/common/packet/impl/PacketStatusInStart.java diff --git a/src/main/java/me/braydon/mc/common/EnumUtils.java b/src/main/java/me/braydon/mc/common/EnumUtils.java new file mode 100644 index 0000000..022a428 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/EnumUtils.java @@ -0,0 +1,26 @@ +package me.braydon.mc.common; + +import lombok.NonNull; +import lombok.experimental.UtilityClass; + +/** + * @author Braydon + */ +@UtilityClass +public final class EnumUtils { + /** + * Get the enum constant of the specified enum type with the specified name. + * + * @param enumType the enum type + * @param name the name of the constant to return + * @param the type of the enum + * @return the enum constant of the specified enum type with the specified name + */ + public > T getEnumConstant(@NonNull Class enumType, @NonNull String name) { + try { + return Enum.valueOf(enumType, name); + } catch (IllegalArgumentException ex) { + return null; + } + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/packet/MinecraftPacket.java b/src/main/java/me/braydon/mc/common/packet/MinecraftPacket.java new file mode 100644 index 0000000..8c99c99 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/packet/MinecraftPacket.java @@ -0,0 +1,65 @@ +package me.braydon.mc.common.packet; + +import lombok.NonNull; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * Represents a packet in + * the Minecraft protocol. + * + * @author Braydon + */ +public abstract class MinecraftPacket { + /** + * Process this packet. + * + * @param inputStream the input stream to read from + * @param outputStream the output stream to write to + * @throws IOException if an I/O error occurs + */ + public abstract void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException; + + /** + * Write a variable integer to the output stream. + * + * @param outputStream the output stream to write to + * @param paramInt the integer to write + * @throws IOException if an I/O error occurs + */ + protected final void writeVarInt(DataOutputStream outputStream, int paramInt) throws IOException { + while (true) { + if ((paramInt & 0xFFFFFF80) == 0) { + outputStream.writeByte(paramInt); + return; + } + outputStream.writeByte(paramInt & 0x7F | 0x80); + paramInt >>>= 7; + } + } + + /** + * Read a variable integer from the input stream. + * + * @param inputStream the input stream to read from + * @return the integer that was read + * @throws IOException if an I/O error occurs + */ + protected final int readVarInt(@NonNull DataInputStream inputStream) throws IOException { + int i = 0; + int j = 0; + while (true) { + int k = inputStream.readByte(); + i |= (k & 0x7F) << j++ * 7; + if (j > 5) { + throw new RuntimeException("VarInt too big"); + } + if ((k & 0x80) != 128) { + break; + } + } + return i; + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/packet/impl/PacketHandshakingInSetProtocol.java b/src/main/java/me/braydon/mc/common/packet/impl/PacketHandshakingInSetProtocol.java new file mode 100644 index 0000000..e5f961c --- /dev/null +++ b/src/main/java/me/braydon/mc/common/packet/impl/PacketHandshakingInSetProtocol.java @@ -0,0 +1,64 @@ +package me.braydon.mc.common.packet.impl; + +import lombok.AllArgsConstructor; +import lombok.NonNull; +import lombok.ToString; +import me.braydon.mc.common.packet.MinecraftPacket; + +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * This packet is sent by the client to the server to set + * the hostname, port, and protocol version of the client. + * + * @author Braydon + * @see Protocol Docs + */ +@AllArgsConstructor @ToString +public final class PacketHandshakingInSetProtocol extends MinecraftPacket { + private static final byte ID = 0x00; // The ID of the packet + private static final int STATUS_HANDSHAKE = 1; // The status handshake ID + + /** + * The hostname of the server. + */ + @NonNull private final String hostname; + + /** + * The port of the server. + */ + private final int port; + + /** + * The protocol version of the server. + */ + private final int protocolVersion; + + /** + * Process this packet. + * + * @param inputStream the input stream to read from + * @param outputStream the output stream to write to + * @throws IOException if an I/O error occurs + */ + @Override + public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException { + try (ByteArrayOutputStream handshakeBytes = new ByteArrayOutputStream(); + DataOutputStream handshake = new DataOutputStream(handshakeBytes) + ) { + handshake.writeByte(ID); // Write the ID of the packet + writeVarInt(handshake, protocolVersion); // Write the protocol version + writeVarInt(handshake, hostname.length()); // Write the length of the hostname + handshake.writeBytes(hostname); // Write the hostname + handshake.writeShort(port); // Write the port + writeVarInt(handshake, STATUS_HANDSHAKE); // Write the status handshake ID + + // Write the handshake bytes to the output stream + writeVarInt(outputStream, handshakeBytes.size()); + outputStream.write(handshakeBytes.toByteArray()); + } + } +} \ No newline at end of file diff --git a/src/main/java/me/braydon/mc/common/packet/impl/PacketStatusInStart.java b/src/main/java/me/braydon/mc/common/packet/impl/PacketStatusInStart.java new file mode 100644 index 0000000..9363fe9 --- /dev/null +++ b/src/main/java/me/braydon/mc/common/packet/impl/PacketStatusInStart.java @@ -0,0 +1,62 @@ +package me.braydon.mc.common.packet.impl; + +import lombok.Getter; +import lombok.NonNull; +import me.braydon.mc.common.packet.MinecraftPacket; + +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +/** + * This packet is sent by the client to the server to request the + * status of the server. The server will respond with a json object + * containing the server's status. + * + * @author Braydon + * @see Protocol Docs + */ +@Getter +public final class PacketStatusInStart extends MinecraftPacket { + private static final byte ID = 0x00; // The ID of the packet + + /** + * The response json from the server, null if none. + */ + private String response; + + /** + * Process this packet. + * + * @param inputStream the input stream to read from + * @param outputStream the output stream to write to + * @throws IOException if an I/O error occurs + */ + @Override + public void process(@NonNull DataInputStream inputStream, @NonNull DataOutputStream outputStream) throws IOException { + // Send the status request + outputStream.writeByte(0x01); // Size of packet + outputStream.writeByte(ID); + + // Read the status response + readVarInt(inputStream); // Size of the response + int id = readVarInt(inputStream); + if (id == -1) { // The stream was prematurely ended + throw new IOException("Server prematurely ended stream."); + } else if (id != ID) { // Invalid packet ID + throw new IOException("Server returned invalid packet ID."); + } + + int length = readVarInt(inputStream); // Length of the response + if (length == -1) { // The stream was prematurely ended + throw new IOException("Server prematurely ended stream."); + } else if (length == 0) { + throw new IOException("Server returned unexpected value."); + } + + // Get the json response + byte[] data = new byte[length]; + inputStream.readFully(data); + response = new String(data); + } +} \ No newline at end of file