Handshake + ServerInfo packets
This commit is contained in:
@@ -23,6 +23,9 @@ dependencies {
|
|||||||
/* LOGGER */
|
/* LOGGER */
|
||||||
compile (group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version)
|
compile (group: 'org.slf4j', name: 'slf4j-api', version: slf4j_version)
|
||||||
|
|
||||||
|
/* COMPONENTS */
|
||||||
|
compile (group: 'com.google.guava', name: 'guava', version: '28.0-jre')
|
||||||
|
|
||||||
/* LOMBOK */
|
/* LOMBOK */
|
||||||
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
annotationProcessor (group: 'org.projectlombok', name: 'lombok', version: lombok_version)
|
||||||
compileOnly (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