Handshake + ServerInfo packets
This commit is contained in:
@@ -23,6 +23,9 @@ dependencies {
|
||||
/* LOGGER */
|
||||
compile (group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version)
|
||||
|
||||
/* COMPONENTS */
|
||||
compile (group: 'com.google.guava', name: 'guava', version: '28.0-jre')
|
||||
|
||||
/* LOMBOK */
|
||||
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
compileOnly (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||
|
||||
8
src/main/java/mc/protocol/DecoderException.java
Normal file
8
src/main/java/mc/protocol/DecoderException.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package mc.protocol;
|
||||
|
||||
public class DecoderException extends RuntimeException {
|
||||
|
||||
public DecoderException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
106
src/main/java/mc/protocol/NetInputStream.java
Normal file
106
src/main/java/mc/protocol/NetInputStream.java
Normal file
@@ -0,0 +1,106 @@
|
||||
package mc.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class NetInputStream extends InputStream {
|
||||
|
||||
public boolean readBoolean() {
|
||||
return readByte() >= 0x01;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return readByte();
|
||||
}
|
||||
|
||||
// Unsigned Byte [1]
|
||||
|
||||
public int readUnsignedShort() {
|
||||
return readShort() & 0xFFFF;
|
||||
}
|
||||
|
||||
// Int [4]
|
||||
|
||||
// Long [8]
|
||||
|
||||
// Float [4]
|
||||
|
||||
// Double [8]
|
||||
|
||||
public String readString() {
|
||||
return readString(Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
public String readString(int maxLength) {
|
||||
int length = readVarInt();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
} else if (length > maxLength) {
|
||||
throw new DecoderException("String length exceeds maximum length: " + length + " > " + maxLength);
|
||||
} else if (length < 0) {
|
||||
throw new DecoderException("String length less zero!");
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length];
|
||||
readBytes(bytes);
|
||||
return new String(bytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
// Chat
|
||||
|
||||
// Identifier
|
||||
|
||||
public int readVarInt() {
|
||||
int numRead = 0;
|
||||
int result = 0;
|
||||
byte read;
|
||||
do {
|
||||
if ((numRead + 1) > 5) {
|
||||
//FIXME выводить в лог предупреждение
|
||||
break; // VarInt is too big
|
||||
}
|
||||
read = readByte();
|
||||
int value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
numRead++;
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// VarLong
|
||||
|
||||
// Entity Metadata
|
||||
|
||||
// Slot
|
||||
|
||||
// NBT Tag
|
||||
|
||||
// Position [8]
|
||||
|
||||
// Angle [1]
|
||||
|
||||
// UUID [16]
|
||||
|
||||
public int readBytes(byte[] buffer) {
|
||||
return readBytes(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer) throws IOException {
|
||||
return readBytes(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read(byte[] buffer, int offset, int length) throws IOException {
|
||||
return readBytes(buffer, offset, length);
|
||||
}
|
||||
|
||||
public abstract byte readByte();
|
||||
public abstract int readBytes(byte[] buffer, int offset, int lengtn);
|
||||
public abstract int readShort();
|
||||
}
|
||||
89
src/main/java/mc/protocol/NetOutputStream.java
Normal file
89
src/main/java/mc/protocol/NetOutputStream.java
Normal file
@@ -0,0 +1,89 @@
|
||||
package mc.protocol;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public abstract class NetOutputStream extends OutputStream {
|
||||
|
||||
public void writeBoolean(boolean value) {
|
||||
writeByte(value ? 0x01 : 0x00);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(int b) throws IOException {
|
||||
writeByte(b);
|
||||
}
|
||||
|
||||
// Unsigned Byte [1]
|
||||
|
||||
// Unsigned Short [2]
|
||||
|
||||
// Int [4]
|
||||
|
||||
// Long [8]
|
||||
|
||||
// Float [4]
|
||||
|
||||
// Double [8]
|
||||
|
||||
public void writeString(String string) {
|
||||
byte[] buf;
|
||||
|
||||
if (string.length() > Short.MAX_VALUE) {
|
||||
//FIXME нужно выдавать предупреждение в лог
|
||||
buf = string.substring(0, Short.MAX_VALUE).getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(Short.MAX_VALUE);
|
||||
} else {
|
||||
buf = string.getBytes(StandardCharsets.UTF_8);
|
||||
writeVarInt(string.length());
|
||||
}
|
||||
|
||||
writeBytes(buf);
|
||||
}
|
||||
|
||||
// Chat
|
||||
|
||||
// Identifier
|
||||
|
||||
public void writeVarInt(int value) {
|
||||
while ((value & -128) != 0) {
|
||||
writeByte(value & 127 | 128);
|
||||
value >>>= 7;
|
||||
}
|
||||
|
||||
writeByte(value);
|
||||
}
|
||||
|
||||
// VarLong
|
||||
|
||||
// Entity Metadata
|
||||
|
||||
// Slot
|
||||
|
||||
// NBT Tag
|
||||
|
||||
// Position [8]
|
||||
|
||||
// Angle [1]
|
||||
|
||||
// UUID [16]
|
||||
|
||||
public void writeBytes(byte[] buffer) {
|
||||
writeBytes(buffer, 0, buffer.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer) throws IOException {
|
||||
writeBytes(buffer);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(byte[] buffer, int offset, int length) throws IOException {
|
||||
writeBytes(buffer, offset, length);
|
||||
}
|
||||
|
||||
public abstract void writeByte(int value);
|
||||
public abstract void writeBytes(byte[] buffer, int offset, int lengtn);
|
||||
public abstract void writeShort(int value);
|
||||
}
|
||||
8
src/main/java/mc/protocol/Packet.java
Normal file
8
src/main/java/mc/protocol/Packet.java
Normal file
@@ -0,0 +1,8 @@
|
||||
package mc.protocol;
|
||||
|
||||
public interface Packet {
|
||||
|
||||
void readSelf(NetInputStream netInputStream);
|
||||
|
||||
void writeSelf(NetOutputStream netOutputStream);
|
||||
}
|
||||
6
src/main/java/mc/protocol/PacketDirection.java
Normal file
6
src/main/java/mc/protocol/PacketDirection.java
Normal file
@@ -0,0 +1,6 @@
|
||||
package mc.protocol;
|
||||
|
||||
public enum PacketDirection {
|
||||
|
||||
SERVER_BOUND, CLIENT_BOUND
|
||||
}
|
||||
67
src/main/java/mc/protocol/State.java
Normal file
67
src/main/java/mc/protocol/State.java
Normal file
@@ -0,0 +1,67 @@
|
||||
package mc.protocol;
|
||||
|
||||
import com.google.common.collect.BiMap;
|
||||
import com.google.common.collect.ImmutableBiMap;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.Setter;
|
||||
import mc.protocol.handshake.client.HandshakePacket;
|
||||
import mc.protocol.status.client.StatusServerRequest;
|
||||
import mc.protocol.status.server.StatusServerResponse;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum State {
|
||||
|
||||
HANDSHAKING(-1){{
|
||||
setServerBoundPackets(ImmutableBiMap.of(
|
||||
0x00, HandshakePacket.class
|
||||
));
|
||||
}},
|
||||
PLAY(0),
|
||||
STATUS(1){{
|
||||
setServerBoundPackets(ImmutableBiMap.of(
|
||||
0x00, StatusServerRequest.class
|
||||
));
|
||||
setClientBoundPackets(ImmutableBiMap.of(
|
||||
0x00, StatusServerResponse.class
|
||||
));
|
||||
}},
|
||||
LOGIN(2);
|
||||
|
||||
public static State getById(int id) {
|
||||
for (State state : State.values()) {
|
||||
if (state.id == id) {
|
||||
return state;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
|
||||
@Setter(value = AccessLevel.PROTECTED)
|
||||
private BiMap<Integer, Class<? extends Packet>> clientBoundPackets;
|
||||
|
||||
@Setter(value = AccessLevel.PROTECTED)
|
||||
private BiMap<Integer, Class<? extends Packet>> serverBoundPackets;
|
||||
|
||||
public Class<? extends Packet> getPacketById(PacketDirection direction, int id) {
|
||||
if (direction == PacketDirection.CLIENT_BOUND) {
|
||||
return clientBoundPackets == null ? null : clientBoundPackets.get(id);
|
||||
} else {
|
||||
return serverBoundPackets == null ? null : serverBoundPackets.get(id);
|
||||
}
|
||||
}
|
||||
|
||||
public Integer getIdByPacket(PacketDirection direction, Class<? extends Packet> clazz) {
|
||||
if (direction == PacketDirection.CLIENT_BOUND) {
|
||||
return clientBoundPackets == null ? null : clientBoundPackets.inverse().get(clazz);
|
||||
} else {
|
||||
return serverBoundPackets == null ? null : serverBoundPackets.inverse().get(clazz);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
package mc.protocol.coder;
|
||||
|
||||
import mc.protocol.NetOutputStream;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
|
||||
class ByteArrayNetOutputStream extends NetOutputStream {
|
||||
|
||||
private ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
@Override
|
||||
public void writeByte(int value) {
|
||||
baos.write(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeBytes(byte[] buffer, int offset, int lengtn) {
|
||||
baos.write(buffer, offset, lengtn);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeShort(int value) {
|
||||
baos.write((value >>> 8) & 0xFF);
|
||||
baos.write(value & 0xFF);
|
||||
}
|
||||
|
||||
int size() {
|
||||
return baos.size();
|
||||
}
|
||||
|
||||
byte[] toByteArray() {
|
||||
return baos.toByteArray();
|
||||
}
|
||||
}
|
||||
45
src/main/java/mc/protocol/coder/ProtocolDecoder.java
Normal file
45
src/main/java/mc/protocol/coder/ProtocolDecoder.java
Normal file
@@ -0,0 +1,45 @@
|
||||
package mc.protocol.coder;
|
||||
|
||||
/*
|
||||
Packet format:
|
||||
|
||||
| FIELD | TYPE | NOTES |
|
||||
+------------+--------+-----------------------------------+
|
||||
| SIZE | VarInt | = sizeOf(id) + sizeOf(byte_array) |
|
||||
| PACKET ID | VarInt | |
|
||||
| BYTE ARRAY | bytes | |
|
||||
|
||||
https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression
|
||||
*/
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.NetInputStream;
|
||||
import mc.protocol.Packet;
|
||||
import mc.protocol.PacketDirection;
|
||||
import mc.protocol.State;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolDecoder {
|
||||
|
||||
private final PacketDirection direction;
|
||||
|
||||
public Packet decode(State state, NetInputStream netInputStream) {
|
||||
//TODO необходим механизм пропуска необработанных байтов
|
||||
int sizePacket = netInputStream.readVarInt();
|
||||
|
||||
int packetId = netInputStream.readVarInt();
|
||||
Class<? extends Packet> packetClass = state.getPacketById(direction, packetId);
|
||||
Objects.requireNonNull(packetClass);
|
||||
|
||||
try {
|
||||
Packet packet = packetClass.newInstance();
|
||||
packet.readSelf(netInputStream);
|
||||
return packet;
|
||||
} catch (InstantiationException | IllegalAccessException e) {
|
||||
e.printStackTrace(); //FIXME нужно писать в лог
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
37
src/main/java/mc/protocol/coder/ProtocolEncoder.java
Normal file
37
src/main/java/mc/protocol/coder/ProtocolEncoder.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package mc.protocol.coder;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.*;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/*
|
||||
Packet format:
|
||||
|
||||
| FIELD | TYPE | NOTES |
|
||||
+------------+--------+-----------------------------------+
|
||||
| SIZE | VarInt | = sizeOf(id) + sizeOf(byte_array) |
|
||||
| PACKET ID | VarInt | |
|
||||
| BYTE ARRAY | bytes | |
|
||||
|
||||
https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression
|
||||
*/
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolEncoder {
|
||||
|
||||
private final PacketDirection direction;
|
||||
|
||||
public void encode(State state, Packet packet, NetOutputStream netOutputStream) {
|
||||
Integer packetId = state.getIdByPacket(direction, packet.getClass());
|
||||
Objects.requireNonNull(packetId);
|
||||
|
||||
ByteArrayNetOutputStream banos = new ByteArrayNetOutputStream();
|
||||
banos.writeVarInt(packetId);
|
||||
packet.writeSelf(banos);
|
||||
|
||||
netOutputStream.writeVarInt(banos.size());
|
||||
netOutputStream.writeBytes(banos.toByteArray());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mc.protocol.handshake.client;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.NetInputStream;
|
||||
import mc.protocol.NetOutputStream;
|
||||
import mc.protocol.Packet;
|
||||
import mc.protocol.State;
|
||||
|
||||
@Data
|
||||
public class HandshakePacket implements Packet {
|
||||
|
||||
private int protocolVersion;
|
||||
private String ip;
|
||||
private int port;
|
||||
private State nextState;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netInputStream) {
|
||||
protocolVersion = netInputStream.readVarInt();
|
||||
ip = netInputStream.readString(255);
|
||||
port = netInputStream.readUnsignedShort();
|
||||
nextState = State.getById(netInputStream.readVarInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netOutputStream) {
|
||||
netOutputStream.writeVarInt(protocolVersion);
|
||||
netOutputStream.writeString(ip);
|
||||
netOutputStream.writeShort(port);
|
||||
netOutputStream.writeVarInt(nextState.getId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package mc.protocol.status.client;
|
||||
|
||||
import mc.protocol.NetInputStream;
|
||||
import mc.protocol.NetOutputStream;
|
||||
import mc.protocol.Packet;
|
||||
|
||||
public class StatusServerRequest implements Packet {
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netInputStream) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netOutputStream) {
|
||||
// empty
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package mc.protocol.status.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.NetInputStream;
|
||||
import mc.protocol.NetOutputStream;
|
||||
import mc.protocol.Packet;
|
||||
|
||||
@Data
|
||||
public class StatusServerResponse implements Packet {
|
||||
|
||||
private String info;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetInputStream netInputStream) {
|
||||
info = netInputStream.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetOutputStream netOutputStream) {
|
||||
netOutputStream.writeString(info);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user