refactoring: swap modules
This commit is contained in:
10
protocol/src/main/java/mc/protocol/ProtocolAttributes.java
Normal file
10
protocol/src/main/java/mc/protocol/ProtocolAttributes.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mc.protocol;
|
||||
|
||||
import io.netty.util.AttributeKey;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ProtocolAttributes {
|
||||
|
||||
public static final AttributeKey<State> STATE = AttributeKey.newInstance("STATE");
|
||||
}
|
||||
10
protocol/src/main/java/mc/protocol/ProtocolConstant.java
Normal file
10
protocol/src/main/java/mc/protocol/ProtocolConstant.java
Normal file
@@ -0,0 +1,10 @@
|
||||
package mc.protocol;
|
||||
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
@UtilityClass
|
||||
public class ProtocolConstant {
|
||||
|
||||
public final String PROTOCOL_NAME = "1.12.2";
|
||||
public final int PROTOCOL_NUMBER = 340;
|
||||
}
|
||||
102
protocol/src/main/java/mc/protocol/State.java
Normal file
102
protocol/src/main/java/mc/protocol/State.java
Normal file
@@ -0,0 +1,102 @@
|
||||
package mc.protocol;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.KeepAlivePacket;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
import mc.protocol.packets.handshaking.client.HandshakePacket;
|
||||
import mc.protocol.packets.login.client.LoginStartPacket;
|
||||
import mc.protocol.packets.login.server.DisconnectPacket;
|
||||
import mc.protocol.packets.login.server.LoginSuccessPacket;
|
||||
import mc.protocol.packets.play.client.*;
|
||||
import mc.protocol.packets.play.server.*;
|
||||
import mc.protocol.packets.status.client.StatusServerRequestPacket;
|
||||
import mc.protocol.packets.status.server.StatusServerResponse;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum State {
|
||||
|
||||
HANDSHAKING(0,
|
||||
// client side
|
||||
Map.of(0x00, HandshakePacket.class),
|
||||
// server side
|
||||
Collections.emptyMap()
|
||||
),
|
||||
|
||||
STATUS(1,
|
||||
// client side
|
||||
Map.of(
|
||||
0x00, StatusServerRequestPacket.class,
|
||||
0x01, KeepAlivePacket.class
|
||||
),
|
||||
// server side
|
||||
Map.of(
|
||||
StatusServerResponse.class, 0x00,
|
||||
KeepAlivePacket.class, 0x01
|
||||
)
|
||||
),
|
||||
|
||||
LOGIN(2,
|
||||
// client side
|
||||
Map.of(0x00, LoginStartPacket.class),
|
||||
// server side
|
||||
Map.of(
|
||||
DisconnectPacket.class, 0x00,
|
||||
LoginSuccessPacket.class, 0x02
|
||||
)
|
||||
),
|
||||
|
||||
PLAY(3,
|
||||
// client side
|
||||
Map.of(
|
||||
0x00, TeleportConfirmPacket.class,
|
||||
0x04, ClientSettingsPacket.class,
|
||||
0x09, PluginMessagePacket.class,
|
||||
0x0B, KeepAlivePacket.class,
|
||||
0x0D, PlayerPositionPacket.class,
|
||||
0x0E, CPlayerPositionAndLookPacket.class,
|
||||
0x0F, PlayerLookPacket.class,
|
||||
0x15, EntityActionPacket.class
|
||||
),
|
||||
// server side
|
||||
Map.of(
|
||||
KeepAlivePacket.class, 0x1F,
|
||||
ChunkDataPacket.class, 0x20,
|
||||
JoinGamePacket.class, 0x23,
|
||||
PlayerAbilitiesPacket.class,0x2C,
|
||||
SPlayerPositionAndLookPacket.class, 0x2F,
|
||||
SpawnPositionPacket.class, 0x46
|
||||
)
|
||||
);
|
||||
|
||||
public static State getById(int id) {
|
||||
// а зачем усложнять?
|
||||
//@formatter:off
|
||||
if (id == 1) return STATUS;
|
||||
else if (id == 2) return LOGIN;
|
||||
else if (id == 3) return PLAY;
|
||||
else return HANDSHAKING;
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int id;
|
||||
|
||||
private final Map<Integer, Class<? extends ClientSidePacket>> clientSidePackets;
|
||||
private final Map<Class<? extends ServerSidePacket>, Integer> serverSidePackets;
|
||||
|
||||
@Nullable
|
||||
public Class<? extends ClientSidePacket> getClientSidePacketById(int id) {
|
||||
return clientSidePackets == null ? null : clientSidePackets.get(id);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Integer getServerSidePacketId(Class<? extends ServerSidePacket> clazz) {
|
||||
return serverSidePackets == null ? null : serverSidePackets.get(clazz);
|
||||
}
|
||||
}
|
||||
192
protocol/src/main/java/mc/protocol/buffer/NetByteBuf.java
Normal file
192
protocol/src/main/java/mc/protocol/buffer/NetByteBuf.java
Normal file
@@ -0,0 +1,192 @@
|
||||
package mc.protocol.buffer;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.experimental.Delegate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.model.text.Text;
|
||||
import mc.protocol.model.text.TextSerializer;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Компонент чтения и записи данных протокола.
|
||||
*
|
||||
* <p>Data types</p>
|
||||
* <pre>
|
||||
* | TYPE | SIZE (bytes) | ENCODING | NOTES |
|
||||
* |----------------|-----------------------|-----------------------------------------------------|--------------------------------------------------------------------------|
|
||||
* | Boolean | 1 | True или False | True = 0x01; False = 0x00 |
|
||||
* | Byte | 1 | Число от -128 до 127 | 8-bit число со знаком |
|
||||
* | Unsigned Byte | 1 | Число от 0 до 255 | 8-bit без знаковое число |
|
||||
* | Short | 2 | Число от -32768 до 32767 | 16-bit число со знаком |
|
||||
* | Unsigned Short | 2 | Число от -32768 до 32767 | 16-bit без знаковое число |
|
||||
* | Int | 4 | Число от -2147483648 и 2147483647 | 32-bit число со знаком |
|
||||
* | Long | 8 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число со знаком |
|
||||
* | Float | 4 | 32-bit число одинарной точности (IEEE 754-2008) | [1] |
|
||||
* | Double | 8 | 64-bit число одинарной точности (IEEE 754-2008) | [2] |
|
||||
* | String (n) | >= 1 ; <= (n * 4) + 3 | Последовательность Unicode scalar values | В начале пишется длина строки в VarInt, после чего записываются символы. |
|
||||
* | | | | Каждый символ может состоять максимум из 4 байт. [3] |
|
||||
* | | | | Максимальная длина строки - 32767 (3 - это как раз размер VarInt для |
|
||||
* | | | | этого числа). |
|
||||
* | VarInt | >= 1 ; <= 5 | Число от -2147483648 и 2147483647 | 32-bit число с плавающей размерностью от 1 до 5 байт |
|
||||
* | VarLong | >= 1 ; <= 10 | Число от -9223372036854775808 и 9223372036854775807 | 64-bit число с плавающей размерностью от 1 до 10 байт |
|
||||
* | Text | | JSON | По файту является String (n), который имеет формат JSON |
|
||||
*
|
||||
* [1] - <a href="https://en.wikipedia.org/wiki/Single-precision_floating-point_format">Single-precision floating-point format</a>
|
||||
* [2] - <a href="https://en.wikipedia.org/wiki/Double-precision_floating-point_format">Double-precision floating-point format</a>
|
||||
* [3] - <a href="http://unicode.org/glossary/#unicode_scalar_value">Unicode Scalar Value</a>
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Data_types&oldid=14345#Definitions">Data types</a>
|
||||
* @see <a href="https://wiki.vg/index.php?title=Chat&oldid=14272">Chat</a>
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ToString
|
||||
public class NetByteBuf extends ByteBuf {
|
||||
|
||||
@Delegate
|
||||
private final ByteBuf byteBuf;
|
||||
|
||||
public void writeUnsignedByte(int value) {
|
||||
byteBuf.writeByte((byte)(value & 0xFF));
|
||||
}
|
||||
|
||||
public void writeText(Text text) {
|
||||
writeString(TextSerializer.toStringPlain(text));
|
||||
}
|
||||
|
||||
//region String
|
||||
public String readString() {
|
||||
return readString(Short.MAX_VALUE);
|
||||
}
|
||||
|
||||
@SuppressWarnings("java:S131")
|
||||
public String readString(int maxLength) {
|
||||
int length = readVarInt();
|
||||
|
||||
if (length == 0) {
|
||||
return "";
|
||||
} else if (length > maxLength) {
|
||||
throw new NetIOException("String length exceeds maximum length: " + length + " > " + maxLength);
|
||||
} else if (length < 0) {
|
||||
throw new NetIOException("String length less zero!");
|
||||
}
|
||||
|
||||
byte[] bytes = new byte[length * 4];
|
||||
int readbleBytes = 0;
|
||||
for (int i = 0; i < length && readableBytes() > 0; i++) {
|
||||
byte b = readByte();
|
||||
bytes[readbleBytes++] = b;
|
||||
|
||||
switch ((b & 0xFF) >> 4) {
|
||||
case 0b1100:
|
||||
case 0b1101:
|
||||
bytes[readbleBytes++] = readByte();
|
||||
break;
|
||||
case 0b1110:
|
||||
bytes[readbleBytes++] = readByte();
|
||||
bytes[readbleBytes++] = readByte();
|
||||
break;
|
||||
case 0b1111:
|
||||
bytes[readbleBytes++] = readByte();
|
||||
bytes[readbleBytes++] = readByte();
|
||||
bytes[readbleBytes++] = readByte();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return new String(bytes, 0, readbleBytes, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public void writeString(String string) {
|
||||
byte[] buf = string.getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
if (buf.length > Short.MAX_VALUE) {
|
||||
log.warn("String is too long: {} > {}", buf.length, Short.MAX_VALUE);
|
||||
writeVarInt(Short.MAX_VALUE);
|
||||
writeBytes(buf, 0, Short.MAX_VALUE);
|
||||
} else {
|
||||
writeVarInt(buf.length);
|
||||
writeBytes(buf);
|
||||
}
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region VarInt
|
||||
public int readVarInt() {
|
||||
int numRead = 0;
|
||||
int result = 0;
|
||||
byte read;
|
||||
do {
|
||||
if ((numRead + 1) > 5) {
|
||||
log.warn("VarInt is too big");
|
||||
break;
|
||||
}
|
||||
read = readByte();
|
||||
int value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
numRead++;
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void writeVarInt(int value) {
|
||||
while ((value & -128) != 0) {
|
||||
writeByte(value & 127 | 128);
|
||||
value >>>= 7;
|
||||
}
|
||||
|
||||
writeByte(value);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region VarLong
|
||||
public long readVarLong() {
|
||||
int numRead = 0;
|
||||
long result = 0L;
|
||||
byte read;
|
||||
do {
|
||||
if (numRead > 10) {
|
||||
log.warn("VarLong is too big");
|
||||
break;
|
||||
}
|
||||
|
||||
read = readByte();
|
||||
long value = (read & 0b01111111);
|
||||
result |= (value << (7 * numRead));
|
||||
|
||||
numRead++;
|
||||
} while ((read & 0b10000000) != 0);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public void writeVarLong(long value) {
|
||||
while ((value & -128L) != 0L) {
|
||||
writeByte((int) (value & 127L) | 128);
|
||||
value >>>= 7;
|
||||
}
|
||||
|
||||
writeByte((int) value);
|
||||
}
|
||||
//endregion
|
||||
|
||||
//region UUID
|
||||
public UUID readUUID() {
|
||||
return new UUID(readLong(), readLong());
|
||||
}
|
||||
|
||||
public void writeUUID(UUID uuid) {
|
||||
writeLong(uuid.getMostSignificantBits());
|
||||
writeLong(uuid.getLeastSignificantBits());
|
||||
}
|
||||
//endregion
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package mc.protocol.buffer;
|
||||
|
||||
public class NetIOException extends RuntimeException {
|
||||
|
||||
public NetIOException(String message) {
|
||||
super(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
package mc.protocol.handler;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.utils.Table;
|
||||
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
public class ProtocolHandlersBus {
|
||||
|
||||
private final Table<State, Class<? extends ClientSidePacket>, Handler> table = new Table<>();
|
||||
|
||||
public <P extends ClientSidePacket> ProtocolHandlersBus addHandler(State state, Class<P> packetClass, Handler<P> handler) {
|
||||
table.put(state, packetClass, handler);
|
||||
return this;
|
||||
}
|
||||
|
||||
public <P extends ClientSidePacket> void process(State state, ChannelHandlerContext ctx, P packet) {
|
||||
Handler handler = table.getColumnAndRow(state, packet.getClass());
|
||||
|
||||
if (handler != null) {
|
||||
handler.handle(ctx, packet);
|
||||
}
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface Handler<P extends ClientSidePacket> {
|
||||
void handle(ChannelHandlerContext ctx, P packet);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mc.protocol.handler;
|
||||
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.channel.SimpleChannelInboundHandler;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolInboundHandler extends SimpleChannelInboundHandler<ClientSidePacket> {
|
||||
|
||||
private static final String CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU = "Программа на вашем хост-компьютере разорвала установленное подключение";
|
||||
|
||||
private final ProtocolHandlersBus protocolHandlersBus;
|
||||
|
||||
@Override
|
||||
protected void channelRead0(ChannelHandlerContext ctx, ClientSidePacket packet) {
|
||||
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||
protocolHandlersBus.process(state, ctx, packet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
|
||||
if (cause instanceof IOException && cause.getLocalizedMessage().equalsIgnoreCase(CLIENT_FORCE_DISCONNECTED_IOEXCEPTION_MESSAGE_RU)) {
|
||||
log.warn("Client '{}' force disconnected", ctx.channel().remoteAddress());
|
||||
if (log.isTraceEnabled()) {
|
||||
log.trace("{}", cause.getMessage(), cause);
|
||||
}
|
||||
} else {
|
||||
log.error("{}", cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package mc.protocol.handler.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.UnknownPacket;
|
||||
import mc.protocol.pool.PacketObjectPool;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ProtocolDecoder extends ByteToMessageDecoder {
|
||||
|
||||
private final boolean readUnknownPackets;
|
||||
|
||||
@Override
|
||||
public void channelActive(ChannelHandlerContext ctx) {
|
||||
ctx.channel().attr(ProtocolAttributes.STATE).set(State.HANDSHAKING);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||
NetByteBuf netByteBuf = new NetByteBuf(in);
|
||||
|
||||
int packetId = netByteBuf.readVarInt();
|
||||
Class<? extends ClientSidePacket> packetClass = state.getClientSidePacketById(packetId);
|
||||
if (packetClass == null) {
|
||||
log.warn("Unknown packet: State {} ; Id 0x{}", state, packetIdAsHexcode(packetId));
|
||||
|
||||
if (readUnknownPackets) {
|
||||
UnknownPacket unknownPacket = new UnknownPacket();
|
||||
unknownPacket.setState(state);
|
||||
unknownPacket.setId(packetId);
|
||||
unknownPacket.setDataSize(netByteBuf.readableBytes());
|
||||
unknownPacket.readSelf(netByteBuf);
|
||||
out.add(unknownPacket);
|
||||
} else {
|
||||
netByteBuf.skipBytes(netByteBuf.readableBytes());
|
||||
}
|
||||
} else {
|
||||
ClientSidePacket packet = PacketObjectPool.getInstance().getPool(packetClass).borrowObject();
|
||||
packet.readSelf(netByteBuf);
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("IN: {}:{}", state, packet);
|
||||
}
|
||||
out.add(packet);
|
||||
}
|
||||
}
|
||||
|
||||
private static String packetIdAsHexcode(int packetId) {
|
||||
String hexPacketId = Integer.toHexString(packetId).toUpperCase();
|
||||
if (hexPacketId.length() == 1) hexPacketId = "0" + hexPacketId;
|
||||
|
||||
return hexPacketId;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package mc.protocol.handler.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import mc.protocol.ProtocolAttributes;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
@Slf4j
|
||||
public class ProtocolEncoder extends MessageToByteEncoder<ServerSidePacket> {
|
||||
|
||||
@Override
|
||||
protected void encode(ChannelHandlerContext ctx, ServerSidePacket packet, ByteBuf out) {
|
||||
State state = Objects.requireNonNull(ctx.channel().attr(ProtocolAttributes.STATE).get());
|
||||
Integer packetId = state.getServerSidePacketId(packet.getClass());
|
||||
if (packetId == null) {
|
||||
log.error("Unknown send packet: State {} ; Class {}", state, packet.getClass());
|
||||
return;
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("OUT: {}:{}", state, packet);
|
||||
}
|
||||
|
||||
NetByteBuf buffer = new NetByteBuf(Unpooled.buffer());
|
||||
buffer.writeVarInt(packetId);
|
||||
packet.writeSelf(buffer);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(out);
|
||||
netByteBuf.writeVarInt(buffer.readableBytes());
|
||||
netByteBuf.writeBytes(buffer);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package mc.protocol.handler.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.ByteToMessageDecoder;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ProtocolSplitter extends ByteToMessageDecoder {
|
||||
|
||||
@Override
|
||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) {
|
||||
NetByteBuf netByteBuf = new NetByteBuf(in);
|
||||
netByteBuf.markReaderIndex();
|
||||
|
||||
do {
|
||||
byte[] sizePacketRaw = new byte[3];
|
||||
for (int i = 0; i < 3; ++i) {
|
||||
sizePacketRaw[i] = netByteBuf.readByte();
|
||||
|
||||
if (sizePacketRaw[i] >= 0) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
int sizePacket = new NetByteBuf(Unpooled.wrappedBuffer(sizePacketRaw)).readVarInt();
|
||||
|
||||
if (netByteBuf.readableBytes() >= sizePacket) {
|
||||
byte[] bytes = new byte[sizePacket];
|
||||
netByteBuf.readBytes(bytes);
|
||||
out.add(Unpooled.wrappedBuffer(bytes));
|
||||
} else {
|
||||
netByteBuf.resetReaderIndex();
|
||||
break;
|
||||
}
|
||||
} while (netByteBuf.readableBytes() > 0);
|
||||
}
|
||||
}
|
||||
18
protocol/src/main/java/mc/protocol/model/Location.java
Normal file
18
protocol/src/main/java/mc/protocol/model/Location.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package mc.protocol.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Location {
|
||||
private double x = 0d;
|
||||
private double y = 0d;
|
||||
private double z = 0d;
|
||||
|
||||
public Location set(double x, double y, double z) {
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
this.z = z;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
16
protocol/src/main/java/mc/protocol/model/Look.java
Normal file
16
protocol/src/main/java/mc/protocol/model/Look.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package mc.protocol.model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class Look {
|
||||
private float yaw;
|
||||
private float pitch;
|
||||
|
||||
public Look set(float yaw, float pitch) {
|
||||
this.yaw = yaw;
|
||||
this.pitch = pitch;
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
50
protocol/src/main/java/mc/protocol/model/ServerInfo.java
Normal file
50
protocol/src/main/java/mc/protocol/model/ServerInfo.java
Normal file
@@ -0,0 +1,50 @@
|
||||
package mc.protocol.model;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.experimental.Accessors;
|
||||
import mc.protocol.model.text.Text;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public class ServerInfo {
|
||||
|
||||
private final Version version = new Version();
|
||||
private final Players players = new Players();
|
||||
|
||||
private Text description;
|
||||
private String favicon;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public static class Version {
|
||||
private String name;
|
||||
private int protocol;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@ToString
|
||||
public static class Players {
|
||||
private int max;
|
||||
private int online;
|
||||
private List<SamplePlayer> sample;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public static class SamplePlayer {
|
||||
private final String id;
|
||||
private final String name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mc.protocol.model;
|
||||
|
||||
import com.eclipsesource.json.Json;
|
||||
import com.eclipsesource.json.JsonArray;
|
||||
import com.eclipsesource.json.JsonObject;
|
||||
import lombok.experimental.UtilityClass;
|
||||
import mc.protocol.model.text.TextSerializer;
|
||||
|
||||
import java.util.Spliterator;
|
||||
import java.util.Spliterators;
|
||||
import java.util.stream.Collector;
|
||||
import java.util.stream.StreamSupport;
|
||||
|
||||
@UtilityClass
|
||||
public class ServerInfoSerializer {
|
||||
|
||||
public String toStringPlain(ServerInfo info) {
|
||||
return toJsonObject(info).toString();
|
||||
}
|
||||
|
||||
private JsonObject toJsonObject(ServerInfo info) {
|
||||
JsonObject jsonObject = Json.object()
|
||||
.add("version", createVersionObj(info))
|
||||
.add("players", createPlayersObj(info))
|
||||
.add("description", TextSerializer.toJsonObject(info.description()));
|
||||
|
||||
if (info.favicon() != null && !info.favicon().isEmpty()) {
|
||||
jsonObject.add("favicon", info.favicon());
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
private JsonObject createVersionObj(ServerInfo info) {
|
||||
return Json.object()
|
||||
.add("name", info.version().name())
|
||||
.add("protocol", info.version().protocol());
|
||||
}
|
||||
|
||||
private JsonObject createPlayersObj(ServerInfo info) {
|
||||
JsonArray sampleArr = info.players().sample().stream()
|
||||
.map(samplePlayer -> Json.object()
|
||||
.add("name", samplePlayer.name())
|
||||
.add("id", samplePlayer.id()))
|
||||
.collect(Collector.of(Json::array, JsonArray::add, ServerInfoSerializer::jsonArrayAddAll));
|
||||
|
||||
return Json.object()
|
||||
.add("max", info.players().max())
|
||||
.add("online", info.players().online())
|
||||
.add("sample", sampleArr);
|
||||
}
|
||||
|
||||
private static JsonArray jsonArrayAddAll(JsonArray jsonArrayTo, JsonArray jsonArrayFrom) {
|
||||
StreamSupport.stream(
|
||||
Spliterators.spliteratorUnknownSize(jsonArrayFrom.iterator(), Spliterator.ORDERED), false)
|
||||
.forEach(jsonArrayTo::add);
|
||||
return jsonArrayTo;
|
||||
}
|
||||
}
|
||||
151
protocol/src/main/java/mc/protocol/model/text/Text.java
Normal file
151
protocol/src/main/java/mc/protocol/model/text/Text.java
Normal file
@@ -0,0 +1,151 @@
|
||||
package mc.protocol.model.text;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import lombok.Getter;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Accessors(fluent = true)
|
||||
@AllArgsConstructor
|
||||
@Data
|
||||
public class Text {
|
||||
|
||||
private TextColor color;
|
||||
private TextStyle style;
|
||||
private String content;
|
||||
private List<Text> children;
|
||||
|
||||
public static Text of(String string) {
|
||||
return new Text(null, null, string, null);
|
||||
}
|
||||
|
||||
public static Text of(TextColor color, String string) {
|
||||
return new Text(color, null, string, null);
|
||||
}
|
||||
|
||||
public static Text of(TextStyle style, String string) {
|
||||
return new Text(null, style, string, null);
|
||||
}
|
||||
|
||||
public static Text of(TextColor color, TextStyle style, String string) {
|
||||
return new Text(color, style, string, null);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public static class Builder {
|
||||
|
||||
@Getter(onMethod = @__(@Nullable))
|
||||
private StringBuilder contentBuilder;
|
||||
private TextStyle.Builder styleBuilder;
|
||||
private TextColor color;
|
||||
private List<Text> children;
|
||||
|
||||
public Builder append(char content) {
|
||||
if (this.contentBuilder == null) {
|
||||
this.contentBuilder = new StringBuilder();
|
||||
}
|
||||
|
||||
this.contentBuilder.append(content);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder append(String content) {
|
||||
if (this.contentBuilder == null) {
|
||||
this.contentBuilder = new StringBuilder(content);
|
||||
} else {
|
||||
this.contentBuilder.append(content);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder append(Text text) {
|
||||
if (children == null) {
|
||||
children = new ArrayList<>();
|
||||
}
|
||||
|
||||
children.add(text);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder style(TextStyle style) {
|
||||
//@formatter:off
|
||||
if (style.bold() != null) bold(style.bold());
|
||||
if (style.italic() != null) italic(style.italic());
|
||||
if (style.underline() != null) underline(style.underline());
|
||||
if (style.strikethrough() != null) strikethrough(style.strikethrough());
|
||||
if (style.obfuscated() != null) obfuscated(style.obfuscated());
|
||||
//@formatter:on
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder color(TextColor color) {
|
||||
this.color = color;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder bold(Boolean bold) {
|
||||
if (this.styleBuilder == null) {
|
||||
this.styleBuilder = TextStyle.builder();
|
||||
}
|
||||
|
||||
this.styleBuilder.bold(bold);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder italic(Boolean italic) {
|
||||
if (this.styleBuilder == null) {
|
||||
this.styleBuilder = TextStyle.builder();
|
||||
}
|
||||
|
||||
this.styleBuilder.italic(italic);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder underline(Boolean underline) {
|
||||
if (this.styleBuilder == null) {
|
||||
this.styleBuilder = TextStyle.builder();
|
||||
}
|
||||
|
||||
this.styleBuilder.underline(underline);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder strikethrough(Boolean strikethrough) {
|
||||
if (this.styleBuilder == null) {
|
||||
this.styleBuilder = TextStyle.builder();
|
||||
}
|
||||
|
||||
this.styleBuilder.strikethrough(strikethrough);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder obfuscated(Boolean obfuscated) {
|
||||
if (this.styleBuilder == null) {
|
||||
this.styleBuilder = TextStyle.builder();
|
||||
}
|
||||
|
||||
this.styleBuilder.obfuscated(obfuscated);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Text build() {
|
||||
return new Text(
|
||||
color,
|
||||
styleBuilder == null ? null : styleBuilder.build(),
|
||||
contentBuilder == null ? null : contentBuilder.toString(),
|
||||
children);
|
||||
}
|
||||
}
|
||||
}
|
||||
30
protocol/src/main/java/mc/protocol/model/text/TextColor.java
Normal file
30
protocol/src/main/java/mc/protocol/model/text/TextColor.java
Normal file
@@ -0,0 +1,30 @@
|
||||
package mc.protocol.model.text;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum TextColor {
|
||||
//@formatter:off
|
||||
BLACK ("black", '0'),
|
||||
DARK_BLUE ("dark_blue", '1'),
|
||||
DARK_GREEN ("dark_green", '2'),
|
||||
DARK_AQUA ("dark_aqua", '3'),
|
||||
DARK_RED ("dark_red", '4'),
|
||||
DARK_PUEPLE("dark_purple", '5'),
|
||||
GOLD ("gold", '6'),
|
||||
GRAY ("gray", '7'),
|
||||
DARK_GRAY ("dark_gray", '8'),
|
||||
BLUE ("blue", '9'),
|
||||
GREEN ("green", 'a'),
|
||||
AQUA ("aqua", 'b'),
|
||||
RED ("red", 'c'),
|
||||
PURPLE ("light_purple",'d'),
|
||||
YELLOW ("yellow", 'e'),
|
||||
WHITE ("white", 'f');
|
||||
//@formatter:on
|
||||
|
||||
private final String name;
|
||||
private final char code;
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package mc.protocol.model.text;
|
||||
|
||||
import com.eclipsesource.json.Json;
|
||||
import com.eclipsesource.json.JsonArray;
|
||||
import com.eclipsesource.json.JsonObject;
|
||||
import lombok.experimental.UtilityClass;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
@UtilityClass
|
||||
public class TextSerializer {
|
||||
|
||||
private static final Map<Character, TextStyle> legacyStyleCodes;
|
||||
private static final Map<Character, TextColor> legacyColorCodes;
|
||||
|
||||
public String toStringPlain(Text text) {
|
||||
return toJsonObject(text).toString();
|
||||
}
|
||||
|
||||
public JsonObject toJsonObject(Text text) {
|
||||
JsonObject jsonObject = Json.object();
|
||||
|
||||
if (text.content() != null) {
|
||||
jsonObject.add("text", text.content());
|
||||
}
|
||||
|
||||
if (text.color() != null) {
|
||||
jsonObject.add("color", text.color().getName());
|
||||
}
|
||||
|
||||
if (text.style() != null) {
|
||||
//@formatter:off
|
||||
if (text.style().bold() != null) jsonObject.add("bold", text.style().bold());
|
||||
if (text.style().italic() != null) jsonObject.add("italic", text.style().italic());
|
||||
if (text.style().underline() != null) jsonObject.add("underline", text.style().underline());
|
||||
if (text.style().strikethrough() != null) jsonObject.add("strikethrough", text.style().strikethrough());
|
||||
if (text.style().obfuscated() != null) jsonObject.add("obfuscated", text.style().obfuscated());
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
if (text.children() != null && !text.children().isEmpty()) {
|
||||
JsonArray extra = Json.array();
|
||||
text.children().forEach(child -> extra.add(toJsonObject(child)));
|
||||
jsonObject.add("extra", extra);
|
||||
}
|
||||
|
||||
return jsonObject;
|
||||
}
|
||||
|
||||
/**
|
||||
* Преобразование строки вида "&4красный" в {@link Text}.
|
||||
*
|
||||
* @param string тест
|
||||
* @return Text
|
||||
*/
|
||||
@SuppressWarnings({"java:S3776", "java:S2583", "java:S135"})
|
||||
public Text fromPlain(String string) {
|
||||
boolean flagSys = false;
|
||||
Text.Builder rootTextBuilder = Text.builder();
|
||||
Text.Builder textBuilder = rootTextBuilder;
|
||||
|
||||
for (char ch : string.toCharArray()) {
|
||||
if (!flagSys) {
|
||||
if ('&' == ch) {
|
||||
flagSys = true;
|
||||
} else {
|
||||
textBuilder.append(ch);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!legacyStyleCodes.containsKey(ch) && !legacyColorCodes.containsKey(ch) && '&' == ch) {
|
||||
textBuilder.append('&');
|
||||
flagSys = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
//noinspection ConstantConditions
|
||||
if (textBuilder.contentBuilder() != null && textBuilder.contentBuilder().length() > 0) {
|
||||
if (textBuilder != rootTextBuilder) {
|
||||
rootTextBuilder.append(textBuilder.build());
|
||||
}
|
||||
textBuilder = Text.builder();
|
||||
}
|
||||
|
||||
if (legacyStyleCodes.containsKey(ch)) {
|
||||
textBuilder.style(legacyStyleCodes.get(ch));
|
||||
} else {
|
||||
textBuilder.color(legacyColorCodes.get(ch));
|
||||
}
|
||||
|
||||
flagSys = false;
|
||||
}
|
||||
|
||||
if (textBuilder != rootTextBuilder) {
|
||||
rootTextBuilder.append(textBuilder.build());
|
||||
}
|
||||
|
||||
return rootTextBuilder.build();
|
||||
}
|
||||
|
||||
static {
|
||||
legacyColorCodes = Map.ofEntries(
|
||||
Map.entry('0', TextColor.BLACK),
|
||||
Map.entry('1', TextColor.DARK_BLUE),
|
||||
Map.entry('2', TextColor.DARK_GREEN),
|
||||
Map.entry('3', TextColor.DARK_AQUA),
|
||||
Map.entry('4', TextColor.DARK_RED),
|
||||
Map.entry('5', TextColor.DARK_PUEPLE),
|
||||
Map.entry('6', TextColor.GOLD),
|
||||
Map.entry('7', TextColor.GRAY),
|
||||
Map.entry('8', TextColor.DARK_GRAY),
|
||||
Map.entry('9', TextColor.BLUE),
|
||||
Map.entry('a', TextColor.GREEN),
|
||||
Map.entry('b', TextColor.AQUA),
|
||||
Map.entry('c', TextColor.RED),
|
||||
Map.entry('d', TextColor.PURPLE),
|
||||
Map.entry('e', TextColor.YELLOW),
|
||||
Map.entry('f', TextColor.WHITE)
|
||||
);
|
||||
|
||||
legacyStyleCodes = Map.of(
|
||||
'k', TextStyle.OBFUSCATED,
|
||||
'l', TextStyle.BOLD,
|
||||
'm', TextStyle.STRIKETHOUGH,
|
||||
'n', TextStyle.UNDERLINE,
|
||||
'o', TextStyle.ITALIC
|
||||
);
|
||||
}
|
||||
}
|
||||
82
protocol/src/main/java/mc/protocol/model/text/TextStyle.java
Normal file
82
protocol/src/main/java/mc/protocol/model/text/TextStyle.java
Normal file
@@ -0,0 +1,82 @@
|
||||
package mc.protocol.model.text;
|
||||
|
||||
import lombok.*;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
@Accessors(fluent = true)
|
||||
@Data
|
||||
@SuppressWarnings("java:S1845")
|
||||
public class TextStyle {
|
||||
|
||||
public static final TextStyle BOLD = new TextStyle(true, null, null, null, null);
|
||||
public static final TextStyle ITALIC = new TextStyle(null, true, null, null, null);
|
||||
public static final TextStyle UNDERLINE = new TextStyle(null, null, true, null, null);
|
||||
public static final TextStyle STRIKETHOUGH = new TextStyle(null, null, null, true, null);
|
||||
public static final TextStyle OBFUSCATED = new TextStyle(null, null, null, null, true);
|
||||
|
||||
public static final TextStyle RESET = new TextStyle(false, false, false, false, false);
|
||||
public static final TextStyle NONE = new TextStyle(null, null, null, null, null);
|
||||
|
||||
private Boolean bold;
|
||||
private Boolean italic;
|
||||
private Boolean underline;
|
||||
private Boolean strikethrough;
|
||||
private Boolean obfuscated;
|
||||
|
||||
public static Builder builder() {
|
||||
return new TextStyle.Builder();
|
||||
}
|
||||
|
||||
@NoArgsConstructor
|
||||
@ToString
|
||||
public static class Builder {
|
||||
|
||||
private Boolean bold;
|
||||
private Boolean italic;
|
||||
private Boolean underline;
|
||||
private Boolean strikethrough;
|
||||
private Boolean obfuscated;
|
||||
|
||||
public Builder bold(Boolean bold) {
|
||||
this.bold = bold;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder italic(Boolean italic) {
|
||||
this.italic = italic;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder underline(Boolean underline) {
|
||||
this.underline = underline;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder strikethrough(Boolean strikethrough) {
|
||||
this.strikethrough = strikethrough;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder obfuscated(Boolean obfuscated) {
|
||||
this.obfuscated = obfuscated;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder merge(TextStyle style) {
|
||||
//@formatter:off
|
||||
if (style.bold != null) this.bold = style.bold;
|
||||
if (style.italic != null) this.italic = style.italic;
|
||||
if (style.underline != null) this.underline = style.underline;
|
||||
if (style.strikethrough != null) this.strikethrough = style.strikethrough;
|
||||
if (style.obfuscated != null) this.obfuscated = style.obfuscated;
|
||||
//@formatter:on
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public TextStyle build() {
|
||||
return new TextStyle(bold, italic, underline, strikethrough, obfuscated);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package mc.protocol.packets;
|
||||
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.utils.pool.Passivable;
|
||||
|
||||
/**
|
||||
* Пакеты отправляемые клиентом.
|
||||
*/
|
||||
public interface ClientSidePacket extends Packet, Passivable {
|
||||
|
||||
void readSelf(NetByteBuf netByteBuf);
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package mc.protocol.packets;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
|
||||
/**
|
||||
* Пинг-пакет.
|
||||
*
|
||||
* <p>Эхо-пакет, которым проверяется качество соединения между <b>Клиентом</b> и <b>Сервером</b>.</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |---------|------|------------------------|
|
||||
* | Payload | Long | Любое уникальное число |
|
||||
* </pre>
|
||||
*
|
||||
* <p>По спецификации:</p>
|
||||
* <oi>
|
||||
* <li>если Сервер не ответил Клиенту в течении 20 секунд, Клиент отключается и выдаёт ошибку "Timed out";</li>
|
||||
* <li>если Клиент не отвечает Серверу в течении 30 секунд, Сервер отключает Клиента.</li>
|
||||
* </oi>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28clientbound.29">Keep Alive (clientbound)</a>
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Keep_Alive_.28serverbound.29">Keep Alive (serverbound)</a>
|
||||
*/
|
||||
@Data
|
||||
public class KeepAlivePacket implements ClientSidePacket, ServerSidePacket {
|
||||
|
||||
private long payload;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
payload = netByteBuf.readLong();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeLong(payload);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.payload = 0;
|
||||
}
|
||||
}
|
||||
19
protocol/src/main/java/mc/protocol/packets/Packet.java
Normal file
19
protocol/src/main/java/mc/protocol/packets/Packet.java
Normal file
@@ -0,0 +1,19 @@
|
||||
package mc.protocol.packets;
|
||||
|
||||
/**
|
||||
* Сетевой пакет.
|
||||
*
|
||||
* <p>Структура</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------------|--------|-------------------------------------------|
|
||||
* | SIZE | VarInt | = sizeOf(PACKET ID) + sizeOf(PACKET DATA) |
|
||||
* | PACKET ID | VarInt | |
|
||||
* | PACKET DATA | bytes | |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Without_compression">Packet without compression</a>
|
||||
*/
|
||||
public interface Packet {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package mc.protocol.packets;
|
||||
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
|
||||
/**
|
||||
* Пакеты отправляемые сервером.
|
||||
*/
|
||||
public interface ServerSidePacket extends Packet {
|
||||
|
||||
void writeSelf(NetByteBuf netByteBuf);
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
package mc.protocol.packets;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@ToString(exclude = "rawData")
|
||||
public class UnknownPacket implements ClientSidePacket {
|
||||
|
||||
private State state;
|
||||
private int id;
|
||||
private int dataSize;
|
||||
private byte[] rawData;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
rawData = new byte[dataSize];
|
||||
netByteBuf.readBytes(rawData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.state = null;
|
||||
this.id = 0;
|
||||
this.dataSize = 0;
|
||||
this.rawData = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package mc.protocol.packets.handshaking.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
/**
|
||||
* Handshake packet.
|
||||
*
|
||||
* <p>Данный пакет заставляет сервер переключить текущий {@link State}</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |------------------|----------------|----------------------------------------------|
|
||||
* | Protocol version | VarInt | Версия протокола [1] |
|
||||
* | Server address | Stirng (255) | Hostname или IP |
|
||||
* | Server port | Unsigned Short | Порт сервера |
|
||||
* | Next state | VarInt | ID State на который необходимо переключиться |
|
||||
*
|
||||
* [1] - <a href="https://wiki.vg/Protocol_version_numbers">Protocol version numbers</a>
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Handshake">Handshake</a>
|
||||
* @see State
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class HandshakePacket implements ClientSidePacket {
|
||||
|
||||
private int protocolVersion;
|
||||
private String host;
|
||||
private int port;
|
||||
private State nextState;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.protocolVersion = netByteBuf.readVarInt();
|
||||
this.host = netByteBuf.readString(255);
|
||||
this.port = netByteBuf.readUnsignedShort();
|
||||
this.nextState = State.getById(netByteBuf.readVarInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.protocolVersion = 0;
|
||||
this.host = null;
|
||||
this.port = 0;
|
||||
this.nextState = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package mc.protocol.packets.login.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
/**
|
||||
* Login start packet.
|
||||
*
|
||||
* <p>Начало авторизации.</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------|--------|------------------|
|
||||
* | Name | String | Имя/Логин игрока |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Login_Start">Login start</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class LoginStartPacket implements ClientSidePacket {
|
||||
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.name = netByteBuf.readString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.name = null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
package mc.protocol.packets.login.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.State;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.text.Text;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Diconnect packet.
|
||||
*
|
||||
* <p>Отключение клиента сервером с указанием причины.</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------------|--------|----------------------------------|
|
||||
* | JSON Reason | Text | Причина отключения. Опционально. |
|
||||
* </pre>
|
||||
*
|
||||
* <p>Пример JSON Reason</p>
|
||||
* <pre>
|
||||
* {
|
||||
* "text": "foo"
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Disconnect_.28login.29">Disconnect (login)</a>
|
||||
* @see State
|
||||
*/
|
||||
@Data
|
||||
public class DisconnectPacket implements ServerSidePacket {
|
||||
|
||||
/**
|
||||
* Причина отключения.
|
||||
*/
|
||||
private Text reason;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeText(reason);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package mc.protocol.packets.login.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Подтверждение успешного логина.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |----------|-------------|-------------------------------|
|
||||
* | UUID | String (36) | Уникальный ID игрока |
|
||||
* | Username | String (16) | Имя игрока, выданное сервером |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Login_Success">Login Success</a>
|
||||
*/
|
||||
@Data
|
||||
public class LoginSuccessPacket implements ServerSidePacket {
|
||||
|
||||
private UUID uuid;
|
||||
private String name;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeString(this.uuid.toString());
|
||||
netByteBuf.writeString(this.name);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.pool.LocationObjectPool;
|
||||
import mc.protocol.pool.LookObjectPool;
|
||||
|
||||
/**
|
||||
* Клиент сообщает о движении и повороте головы Игрока.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-----------|---------|------------------------------------------------------------|
|
||||
* | X | Double | Абсолютная позиция по X |
|
||||
* | Y | Double | Абсолютная позиция по Y. |
|
||||
* | | | Имеется ввиду позиция ног. Голова находиться выше на 1.62f |
|
||||
* | Z | Double | Абсолютная позиция по Z |
|
||||
* | Yaw | Float | Абсолютный поворот головы по OX, в градусах |
|
||||
* | Pitch | Float | Абсолютный поворот головы по OY, в градусах |
|
||||
* | On Ground | Boolean | true, если Игрок находится на земле |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position_And_Look_.28serverbound.29">Player Position And Look (serverbound)</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class CPlayerPositionAndLookPacket implements ClientSidePacket {
|
||||
|
||||
private Location position;
|
||||
private Look look;
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
double x = netByteBuf.readDouble();
|
||||
double y = netByteBuf.readDouble();
|
||||
double z = netByteBuf.readDouble();
|
||||
this.position = LocationObjectPool.borrowObject().set(x, y, z);
|
||||
|
||||
float yaw = netByteBuf.readFloat();
|
||||
float pitch = netByteBuf.readFloat();
|
||||
this.look = LookObjectPool.borrowObject().set(yaw, pitch);
|
||||
|
||||
this.onGround = netByteBuf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
LocationObjectPool.returnObject(this.position);
|
||||
this.position = null;
|
||||
LookObjectPool.returnObject(this.look);
|
||||
this.look = null;
|
||||
this.onGround = false;
|
||||
}
|
||||
|
||||
public double getYPositionHead() {
|
||||
return this.position.getY() + 1.62f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.*;
|
||||
import mc.protocol.utils.ChatMode;
|
||||
import mc.protocol.utils.MainHand;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
/**
|
||||
* Client settings packet.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------------------- |---------------|---------------------------------------------------|
|
||||
* | Locale | String (16) | например en_gb |
|
||||
* | View Distance | Byte | Дистанция отрисовки со стороны Клиента, в чанках. |
|
||||
* | Chat Mode | VarInt | 0: enabled |
|
||||
* | | | 1: commands only |
|
||||
* | | | 2: hidden |
|
||||
* | | | [1] |
|
||||
* | Chat Colors | Boolean | “Colors” multiplayer setting (???) |
|
||||
* | Displayed Skin Parts | Unsigned Byte | битовая маска отображения скина. См. ниже |
|
||||
* | Main Hand | VarInt | 0: Left |
|
||||
* | | | 1: Right |
|
||||
*
|
||||
* [1] - <a href="https://wiki.vg/index.php?title=Chat&oldid=13165#Processing_chat">Processing chat</a>
|
||||
* </pre>
|
||||
*
|
||||
* <p>Биты "Displayed Skin Parts"</p>
|
||||
* <pre>
|
||||
* Bit 0 (0x01): Плащ (Cape)
|
||||
* Bit 1 (0x02): Рубашка (Jacket)
|
||||
* Bit 2 (0x04): Левый рукав (Left Sleeve)
|
||||
* Bit 3 (0x08): Правый рукав (Right Sleeve)
|
||||
* Bit 4 (0x10): Левая штанина (Left Pants Leg)
|
||||
* Bit 5 (0x20): Правая штанина (Right Pants Leg)
|
||||
* Bit 6 (0x40): Шлем (Hat)
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Client_Settings">Client Settings</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class ClientSettingsPacket implements ClientSidePacket {
|
||||
|
||||
private String locale;
|
||||
private int viewDistance;
|
||||
private ChatMode chatMode;
|
||||
private boolean chatColors;
|
||||
@SuppressWarnings("java:S116")
|
||||
private int $displayedSkinPartsBitMask;
|
||||
private MainHand mainHand;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.locale = netByteBuf.readString(16);
|
||||
this.viewDistance = netByteBuf.readByte();
|
||||
this.chatMode = ChatMode.valueById(netByteBuf.readVarInt());
|
||||
this.chatColors = netByteBuf.readBoolean();
|
||||
this.$displayedSkinPartsBitMask = netByteBuf.readUnsignedByte();
|
||||
this.mainHand = MainHand.valueById(netByteBuf.readVarInt());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.locale = null;
|
||||
this.viewDistance = 0;
|
||||
this.chatMode = null;
|
||||
this.chatColors = false;
|
||||
this.$displayedSkinPartsBitMask = 0;
|
||||
this.mainHand = null;
|
||||
}
|
||||
|
||||
public boolean isCapeEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x01) > 0;
|
||||
}
|
||||
|
||||
public boolean isJacketEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x02) > 0;
|
||||
}
|
||||
|
||||
public boolean isLeftSleeveEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x04) > 0;
|
||||
}
|
||||
|
||||
public boolean isRightSleeveEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x08) > 0;
|
||||
}
|
||||
|
||||
public boolean isLeftPantsEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x10) > 0;
|
||||
}
|
||||
|
||||
public boolean isRightPantsEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x20) > 0;
|
||||
}
|
||||
|
||||
public boolean isHatEnabled() {
|
||||
return ($displayedSkinPartsBitMask & 0x40) > 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.*;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
/**
|
||||
* Entity Action packet.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |------------|--------|-------------------------------------------|
|
||||
* | Entity ID | VarInt | ID игрока |
|
||||
* | Action ID | VarInt | ID действия |
|
||||
* | Jump Boost | VarInt | Используется только при "Action ID" = 5. |
|
||||
* | | | В этом случае значение будет от 0 до 100. |
|
||||
* | | | В остальных случаях значение 0. |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Entity_Action" target="_top">Entity Action</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class EntityActionPacket implements ClientSidePacket {
|
||||
|
||||
private Integer entityId;
|
||||
private Action action;
|
||||
private Integer jumpBoost;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.entityId = netByteBuf.readVarInt();
|
||||
int actionId = netByteBuf.readVarInt();
|
||||
this.jumpBoost = netByteBuf.readVarInt();
|
||||
|
||||
this.action = Action.valueOfCode(actionId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.entityId = null;
|
||||
this.action = null;
|
||||
this.jumpBoost = null;
|
||||
}
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public enum Action {
|
||||
START_SNEAKING(0),
|
||||
STOP_SNEAKING(1),
|
||||
LEAVE_BED(2),
|
||||
START_SPRINTING(3),
|
||||
STOP_SPRINTING(4),
|
||||
START_JUMP_WITH_HORSE(5),
|
||||
STOP_JUMP_WITH_HORSE(6),
|
||||
OPEN_HORSE_INVENTORY(7),
|
||||
START_FLYING_WITH_ELYTRA(8);
|
||||
|
||||
@Nullable
|
||||
public static Action valueOfCode(int code) {
|
||||
for (Action action : Action.values()) {
|
||||
if (action.code == code) {
|
||||
return action;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Getter
|
||||
private final int code;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.pool.LookObjectPool;
|
||||
|
||||
/**
|
||||
* Клиент сообщает о повороте головы Игрока.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-----------|---------|---------------------------------------------|
|
||||
* | Yaw | Float | Абсолютный поворот головы по OX, в градусах |
|
||||
* | Pitch | Float | Абсолютный поворот головы по OY, в градусах |
|
||||
* | On Ground | Boolean | true, если Игрок находится на земле |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Look">Player Look</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class PlayerLookPacket implements ClientSidePacket {
|
||||
|
||||
private Look look;
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
float yaw = netByteBuf.readFloat();
|
||||
float pitch = netByteBuf.readFloat();
|
||||
this.look = LookObjectPool.borrowObject().set(yaw, pitch);
|
||||
|
||||
this.onGround = netByteBuf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
LookObjectPool.returnObject(this.look);
|
||||
this.look = null;
|
||||
this.onGround = false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.pool.LocationObjectPool;
|
||||
|
||||
/**
|
||||
* Клиент сообщает о движении Игрока.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-----------|---------|-------------------------------------|
|
||||
* | X | Double | Абсолютная позиция по X |
|
||||
* | Feet Y | Double | Абсолютная позиция ног по Y. |
|
||||
* | | | Голова находиться выше на 1.62f |
|
||||
* | Z | Double | Абсолютная позиция по Z |
|
||||
* | On Ground | Boolean | true, если Игрок находится на земле |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position">Player Position</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class PlayerPositionPacket implements ClientSidePacket {
|
||||
|
||||
private Location position;
|
||||
private boolean onGround;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
double x = netByteBuf.readDouble();
|
||||
double y = netByteBuf.readDouble();
|
||||
double z = netByteBuf.readDouble();
|
||||
this.position = LocationObjectPool.borrowObject().set(x, y, z);
|
||||
|
||||
this.onGround = netByteBuf.readBoolean();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
LocationObjectPool.returnObject(this.position);
|
||||
this.position = null;
|
||||
this.onGround = false;
|
||||
}
|
||||
|
||||
public double getYPositionHead() {
|
||||
return this.position.getY() + 1.62f;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
|
||||
/**
|
||||
* Plugin Message packet.
|
||||
*
|
||||
* <p>Канал связи для модов и плагинов.</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |--------------|-------------|------------------|
|
||||
* | Channel name | String (20) | Название канала |
|
||||
* | Data | Byte array | Любые данные |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Plugin_Message_.28serverbound.29">Plugin Message (serverbound)</a>
|
||||
* @see <a href="https://wiki.vg/index.php?title=Plugin_channels&oldid=14089">Plugin channels</a>
|
||||
* @see <a href="https://dinnerbone.com/blog/2012/01/13/minecraft-plugin-channels-messaging/">Minecraft Plugin Channels + Messaging</a>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class PluginMessagePacket implements ClientSidePacket {
|
||||
|
||||
private String channelName;
|
||||
private byte[] rawData;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.channelName = netByteBuf.readString(20);
|
||||
this.rawData = new byte[netByteBuf.readableBytes()];
|
||||
netByteBuf.readBytes(this.rawData);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.channelName = null;
|
||||
this.rawData = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package mc.protocol.packets.play.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.play.server.SPlayerPositionAndLookPacket;
|
||||
|
||||
/**
|
||||
* Teleport сonfirm packet.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------------|--------|------------------------------------------------------------|
|
||||
* | Teleport ID | VarInt | ID, который был выдан пакетом {@link SPlayerPositionAndLookPacket} |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=7368#Login_Start" target="_top">Login start</a>
|
||||
* @see SPlayerPositionAndLookPacket
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@Getter
|
||||
@EqualsAndHashCode
|
||||
@ToString
|
||||
public class TeleportConfirmPacket implements ClientSidePacket {
|
||||
|
||||
private int teleportId;
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
this.teleportId = netByteBuf.readVarInt();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
this.teleportId = 0;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package mc.protocol.packets.play.server;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Данные чанка.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |--------------------------|------------- |------------------------------------------------------------------------------------|
|
||||
* | Chunk X | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
|
||||
* | Chunk Z | Integer | Координаты чанка (координата блока, делённая на 16, округленная в меньшую сторону) |
|
||||
* | Is Full chunk | Boolean | См. Chunk Format |
|
||||
* | Available Sections | VarInt | Битовая маска, где каждый бит - это часть чанка (0-15) |
|
||||
* | Size of Data | VarInt | Размер поля "Data" |
|
||||
* | Data | Byte array | Данные чанка. См. Chunk Format |
|
||||
* | Number of block entities | VarInt | Количество элементов в поле "Block entities" |
|
||||
* | Block entities | Array of NBT | Все сущности в чанке |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Chunk_Data">Chunk Data</a>
|
||||
* @see <a href="https://wiki.vg/index.php?title=Chunk_Format&oldid=14135">Chunk Format</a>
|
||||
*/
|
||||
@Data
|
||||
public class ChunkDataPacket implements ServerSidePacket {
|
||||
|
||||
private static NetByteBuf voidData;
|
||||
|
||||
private int x;
|
||||
private int z;
|
||||
|
||||
@SuppressWarnings("java:S125")
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeInt(x);
|
||||
netByteBuf.writeInt(z);
|
||||
/* Временное отключение кода
|
||||
netByteBuf.writeBoolean(true); // Is Full chunk
|
||||
netByteBuf.writeVarInt(0b11111111); // Available Sections
|
||||
|
||||
NetByteBuf data = new NetByteBuf(Unpooled.buffer());
|
||||
// <Data>
|
||||
for (int i = 0; i < 16; i++) {
|
||||
NetByteBuf dataBuff = new NetByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
|
||||
NetByteBuf blockLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
||||
NetByteBuf skyLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
||||
NetByteBuf biomes = new NetByteBuf(Unpooled.wrappedBuffer(new byte[256]));
|
||||
|
||||
// <Chunk Section>
|
||||
data.writeUnsignedByte(13); // Bits Per Block
|
||||
// <Palette>
|
||||
data.writeUnsignedByte(0); // Palette Length (for direct)
|
||||
// <Palette Data/>
|
||||
// </Palette>
|
||||
data.writeVarInt(dataBuff.readableBytes()); // Data Array Length
|
||||
data.writeBytes(dataBuff); // Data Array
|
||||
data.writeBytes(blockLight); // Block Light
|
||||
data.writeBytes(skyLight); // Sky Light
|
||||
// </Chunk Section>
|
||||
data.writeBytes(biomes); // Biomes
|
||||
}
|
||||
// </Data>
|
||||
|
||||
netByteBuf.writeVarInt(data.readableBytes()); // Size of Data
|
||||
netByteBuf.writeBytes(data); // Data
|
||||
netByteBuf.writeVarInt(0); // Number of block entities
|
||||
// write NBT's
|
||||
*/
|
||||
|
||||
netByteBuf.writeBytes(voidData);
|
||||
|
||||
voidData.resetReaderIndex();
|
||||
voidData.resetWriterIndex();
|
||||
}
|
||||
|
||||
static {
|
||||
voidData = new NetByteBuf(Unpooled.buffer());
|
||||
voidData.writeBoolean(true); // Is Full chunk
|
||||
voidData.writeVarInt(0b11111111); // Available Sections
|
||||
|
||||
NetByteBuf data = new NetByteBuf(Unpooled.buffer());
|
||||
for (int i = 0; i < 16; i++) {
|
||||
NetByteBuf dataBuff = new NetByteBuf(Unpooled.wrappedBuffer(new byte[4096]));
|
||||
NetByteBuf blockLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
||||
NetByteBuf skyLight = new NetByteBuf(Unpooled.wrappedBuffer(new byte[2048]));
|
||||
NetByteBuf biomes = new NetByteBuf(Unpooled.wrappedBuffer(new byte[256]));
|
||||
|
||||
data.writeUnsignedByte(13);
|
||||
data.writeUnsignedByte(0);
|
||||
data.writeVarInt(dataBuff.readableBytes());
|
||||
data.writeBytes(dataBuff);
|
||||
data.writeBytes(blockLight);
|
||||
data.writeBytes(skyLight);
|
||||
data.writeBytes(biomes);
|
||||
}
|
||||
|
||||
voidData.writeVarInt(data.readableBytes());
|
||||
voidData.writeBytes(data);
|
||||
voidData.writeVarInt(0);
|
||||
|
||||
voidData.markReaderIndex();
|
||||
voidData.markWriterIndex();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mc.protocol.packets.play.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.utils.Difficulty;
|
||||
import mc.protocol.utils.GameMode;
|
||||
import mc.protocol.utils.LevelType;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Join game packet.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |--------------------|---------------|----------------------------------------------------------------------|
|
||||
* | Entity ID | Integer | ID сущности (игрока) |
|
||||
* | Gamemode | Unsigned Byte | 0: Survival |
|
||||
* | | | 1: Creative |
|
||||
* | | | 2: Adventure |
|
||||
* | | | 3: Spectator |
|
||||
* | | | Bit 3 (0x8) is the hardcore flag. |
|
||||
* | Dimension | Integer | -1: Nether |
|
||||
* | | | 0: Overworld |
|
||||
* | | | 1: End |
|
||||
* | Difficulty | Unsigned Byte | 0: peaceful |
|
||||
* | | | 1: easy |
|
||||
* | | | 2: normal |
|
||||
* | | | 3: hard |
|
||||
* | Max Players | Unsigned Byte | Когда-то использовался клиентом для |
|
||||
* | | | отображения списка игроков. Теперь не используется |
|
||||
* | Level Type | String (16) | Принимает одно из значений: |
|
||||
* | | | default, flat, largeBiomes, amplified, default_1_1 |
|
||||
* | Reduced Debug Info | Boolean | Если true, то Клиент отображает меньше отладочной информации (в F3?) |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Join_Game">Join Game</a>
|
||||
*/
|
||||
@Data
|
||||
public class JoinGamePacket implements ServerSidePacket {
|
||||
|
||||
private int entityId;
|
||||
private GameMode gameMode;
|
||||
private int dimension;
|
||||
private Difficulty difficulty;
|
||||
private LevelType levelType;
|
||||
private boolean reducedDebugInfo;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeInt(entityId);
|
||||
netByteBuf.writeUnsignedByte(gameMode.getId());
|
||||
netByteBuf.writeInt(dimension);
|
||||
netByteBuf.writeUnsignedByte(difficulty.getId());
|
||||
netByteBuf.writeUnsignedByte(0); // Max Players, unused
|
||||
netByteBuf.writeString(levelType.getType());
|
||||
netByteBuf.writeBoolean(reducedDebugInfo);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
package mc.protocol.packets.play.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Характеристики игрока.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |------------------------------|----------|-----------------------------------------|
|
||||
* | Flags | Byte | Битовая маска флагов. См. ниже значения |
|
||||
* | Flying Speed | Float | Скорость полёта |
|
||||
* | Field of View (FOV) Modifier | Float | Поле зрения |
|
||||
* </pre>
|
||||
*
|
||||
* <p>Флаги "Flags"</p>
|
||||
* <pre>
|
||||
* Bit 0x01 - Неуязвимость (Invulnerable)
|
||||
* Bit 0x02 - В полёте (Flying)
|
||||
* Bit 0x04 - Может летать (Allow Flying)
|
||||
* Bit 0x08 - Creative Mode (Instant Break)
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Abilities_.28clientbound.29">Player Abilities</a>
|
||||
*/
|
||||
@Data
|
||||
public class PlayerAbilitiesPacket implements ServerSidePacket {
|
||||
|
||||
@SuppressWarnings("java:S116")
|
||||
private byte $flags = 0;
|
||||
private float flyingSpeed;
|
||||
private float fieldOfView;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeByte(this.$flags);
|
||||
netByteBuf.writeFloat(this.flyingSpeed);
|
||||
netByteBuf.writeFloat(this.fieldOfView);
|
||||
}
|
||||
|
||||
//FIXME использование value значений
|
||||
public void setInvulnerable(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x01);
|
||||
}
|
||||
|
||||
public void setFlying(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x02);
|
||||
}
|
||||
|
||||
public void setCatFly(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x04);
|
||||
}
|
||||
|
||||
public void setCreativeMode(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x08);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package mc.protocol.packets.play.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.model.Look;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
import mc.protocol.packets.play.client.TeleportConfirmPacket;
|
||||
|
||||
/**
|
||||
* Установка позиции и угла осмотра Игрока.
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |-------------|--------|-----------------------------------------------------------------------------------|
|
||||
* | X | Double | Абсолютная или относительная позиция по X. Зависит от "Flags" |
|
||||
* | Y | Double | Абсолютная или относительная позиция по Y. Зависит от "Flags" |
|
||||
* | Z | Double | Абсолютная или относительная позиция по Z. Зависит от "Flags" |
|
||||
* | Yaw | Float | Абсолютный или относительный поворот головы по OX, в градусах. Зависит от "Flags" |
|
||||
* | Pitch | Float | Абсолютный или относительный поворот головы по OY, в градусах. Зависит от "Flags" |
|
||||
* | Flags | Byte | Битовая маска значений флагов. См. значения ниже |
|
||||
* | Teleport ID | VarInt | ID для подтверждения клиентом перемещения Игрока |
|
||||
* </pre>
|
||||
*
|
||||
* <p>Значения "Flags"</p>
|
||||
* <pre>
|
||||
* | Field | Bit |
|
||||
* |-------|------|
|
||||
* | X | 0x01 |
|
||||
* | Y | 0x02 |
|
||||
* | Z | 0x04 |
|
||||
* | X_ROT | 0x08 |
|
||||
* | Y_ROT | 0x10 |
|
||||
* </pre>
|
||||
*
|
||||
* <p>Примечание от Dinnerbone про "Flags":</p>
|
||||
* <i>"It's a bitfield, X/Y/Z/Y_ROT/X_ROT. If X is set, the x value is relative and not absolute."</i>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Player_Position_And_Look_.28clientbound.29">Player Position And Look (clientbound)</a>
|
||||
* @see TeleportConfirmPacket
|
||||
*/
|
||||
@Data
|
||||
public class SPlayerPositionAndLookPacket implements ServerSidePacket {
|
||||
|
||||
private Location position;
|
||||
private Look look;
|
||||
@SuppressWarnings("java:S116")
|
||||
private byte $flags = 0;
|
||||
private int teleportId;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeDouble(this.position.getX());
|
||||
netByteBuf.writeDouble(this.position.getY());
|
||||
netByteBuf.writeDouble(this.position.getZ());
|
||||
netByteBuf.writeFloat(this.look.getYaw());
|
||||
netByteBuf.writeFloat(this.look.getPitch());
|
||||
netByteBuf.writeByte(this.$flags);
|
||||
netByteBuf.writeVarInt(teleportId);
|
||||
}
|
||||
|
||||
//FIXME использовать value значения
|
||||
public void setFlagX(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x01);
|
||||
}
|
||||
|
||||
public void setFlagY(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x02);
|
||||
}
|
||||
|
||||
public void setFlagZ(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x04);
|
||||
}
|
||||
|
||||
public void setFlagXRot(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x08);
|
||||
}
|
||||
|
||||
public void setFlagYRot(boolean value) {
|
||||
this.$flags = (byte) (this.$flags | 0x10);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package mc.protocol.packets.play.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Спавн позиция игрока.
|
||||
*
|
||||
* <p>Используется призаходе игрока на сервер.</p>
|
||||
*
|
||||
* <p>Структура пакета</p>
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |----------|----------|-----------------------|
|
||||
* | Location | Position | Локация спавна игрока |
|
||||
* </pre>
|
||||
*
|
||||
* @see <a href="https://wiki.vg/index.php?title=Protocol&oldid=14204#Spawn_Position">Spawn Position</a>
|
||||
*/
|
||||
@Data
|
||||
public class SpawnPositionPacket implements ServerSidePacket {
|
||||
|
||||
private Location spawn;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
long spawnSerialized =
|
||||
((long) (floorDouble(spawn.getX()) & 0x3FFFFFF) << 38)
|
||||
| ((long) (floorDouble(spawn.getY()) & 0xFFF) << 26)
|
||||
| (floorDouble(spawn.getZ()) & 0x3FFFFFF);
|
||||
netByteBuf.writeLong(spawnSerialized);
|
||||
}
|
||||
|
||||
private static int floorDouble(double value) {
|
||||
int i = (int) value;
|
||||
return value < (double) i ? i - 1 : i;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package mc.protocol.packets.status.client;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.ToString;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Status server packet, request.
|
||||
*
|
||||
* <p>Клиент запрашивает получение информации о сервере</p>
|
||||
*/
|
||||
@NoArgsConstructor
|
||||
@EqualsAndHashCode(callSuper = false)
|
||||
@ToString
|
||||
public class StatusServerRequestPacket implements ClientSidePacket, ServerSidePacket {
|
||||
|
||||
@Override
|
||||
public void readSelf(NetByteBuf netByteBuf) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
// empty
|
||||
}
|
||||
|
||||
@Override
|
||||
public void passivate() {
|
||||
// pass
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mc.protocol.packets.status.server;
|
||||
|
||||
import lombok.Data;
|
||||
import mc.protocol.buffer.NetByteBuf;
|
||||
import mc.protocol.packets.ServerSidePacket;
|
||||
|
||||
/**
|
||||
* Status server packet, response.
|
||||
*
|
||||
* <p>Информация о сервере</p>
|
||||
*
|
||||
* <p>Структура пакета
|
||||
* <pre>
|
||||
* | FIELD | TYPE | NOTES |
|
||||
* |---------------|--------|-----------------------------------------|
|
||||
* | JSON Response | String | Информация о сервере в JSON формате [1] |
|
||||
*
|
||||
* [1] - <a href="https://wiki.vg/index.php?title=Server_List_Ping&oldid=7555#Response" target="_top">Server List Ping: Response</a>
|
||||
* </pre>
|
||||
*
|
||||
* <p>Пример JSON Response</p>
|
||||
* <pre>
|
||||
* {
|
||||
* "version": {
|
||||
* "name": "1.8.7",
|
||||
* "protocol": 47
|
||||
* },
|
||||
* "players": {
|
||||
* "max": 100,
|
||||
* "online": 5,
|
||||
* "sample": [
|
||||
* {
|
||||
* "name": "thinkofdeath",
|
||||
* "id": "4566e69f-c907-48ee-8d71-d7ba5aa00d20"
|
||||
* }
|
||||
* ]
|
||||
* },
|
||||
* "description": {
|
||||
* "text": "Hello world"
|
||||
* },
|
||||
* "favicon": "data:image/png;base64,<data>"
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* <p><code>`$.favicon`</code> должен быть формата PNG и размеры 64x64 px</p>
|
||||
*/
|
||||
@Data
|
||||
public class StatusServerResponse implements ServerSidePacket {
|
||||
|
||||
/**
|
||||
* Информация о серере.
|
||||
*/
|
||||
private String info;
|
||||
|
||||
@Override
|
||||
public void writeSelf(NetByteBuf netByteBuf) {
|
||||
netByteBuf.writeString(info);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
package mc.protocol.pool;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import mc.protocol.model.Location;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class LocationObjectPool extends BasePooledObjectFactory<Location> {
|
||||
|
||||
private static final ObjectPool<Location> instance = new GenericObjectPool<>(new LocationObjectPool());
|
||||
|
||||
@Override
|
||||
public Location create() {
|
||||
return new Location();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<Location> wrap(Location location) {
|
||||
return new DefaultPooledObject<>(location);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Location borrowObject() {
|
||||
return instance.borrowObject();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void returnObject(Location location) {
|
||||
instance.returnObject(location);
|
||||
}
|
||||
}
|
||||
37
protocol/src/main/java/mc/protocol/pool/LookObjectPool.java
Normal file
37
protocol/src/main/java/mc/protocol/pool/LookObjectPool.java
Normal file
@@ -0,0 +1,37 @@
|
||||
package mc.protocol.pool;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.SneakyThrows;
|
||||
import mc.protocol.model.Look;
|
||||
import org.apache.commons.pool2.BasePooledObjectFactory;
|
||||
import org.apache.commons.pool2.ObjectPool;
|
||||
import org.apache.commons.pool2.PooledObject;
|
||||
import org.apache.commons.pool2.impl.DefaultPooledObject;
|
||||
import org.apache.commons.pool2.impl.GenericObjectPool;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class LookObjectPool extends BasePooledObjectFactory<Look> {
|
||||
|
||||
private static final ObjectPool<Look> instance = new GenericObjectPool<>(new LookObjectPool());
|
||||
|
||||
@Override
|
||||
public Look create() {
|
||||
return new Look();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PooledObject<Look> wrap(Look look) {
|
||||
return new DefaultPooledObject<>(look);
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static Look borrowObject() {
|
||||
return instance.borrowObject();
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
public static void returnObject(Look look) {
|
||||
instance.returnObject(look);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package mc.protocol.pool;
|
||||
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import mc.protocol.packets.ClientSidePacket;
|
||||
import mc.utils.pool.WideClassObjectPool;
|
||||
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public class PacketObjectPool extends WideClassObjectPool<ClientSidePacket> {
|
||||
|
||||
@Getter
|
||||
private static final PacketObjectPool instance = new PacketObjectPool();
|
||||
}
|
||||
20
protocol/src/main/java/mc/protocol/utils/ChatMode.java
Normal file
20
protocol/src/main/java/mc/protocol/utils/ChatMode.java
Normal file
@@ -0,0 +1,20 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public enum ChatMode {
|
||||
FULL,
|
||||
COMMANDS_ONLY,
|
||||
HIDDEN;
|
||||
|
||||
@Nullable
|
||||
public static ChatMode valueById(int id) {
|
||||
// а зачем усложнять?
|
||||
//@formatter:off
|
||||
if (id == 1) return FULL;
|
||||
else if (id == 2) return COMMANDS_ONLY;
|
||||
else if (id == 3) return HIDDEN;
|
||||
else return null;
|
||||
//@formatter:on
|
||||
}
|
||||
}
|
||||
15
protocol/src/main/java/mc/protocol/utils/Difficulty.java
Normal file
15
protocol/src/main/java/mc/protocol/utils/Difficulty.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum Difficulty {
|
||||
PEACEFUL(0),
|
||||
EASY(1),
|
||||
NORMAL(2),
|
||||
HARD(3);
|
||||
|
||||
private final int id;
|
||||
}
|
||||
15
protocol/src/main/java/mc/protocol/utils/GameMode.java
Normal file
15
protocol/src/main/java/mc/protocol/utils/GameMode.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum GameMode {
|
||||
SURVIVAL(0),
|
||||
CREATIVE(1),
|
||||
ADVENTURE(2),
|
||||
SPECTATOR(3);
|
||||
|
||||
private final int id;
|
||||
}
|
||||
16
protocol/src/main/java/mc/protocol/utils/LevelType.java
Normal file
16
protocol/src/main/java/mc/protocol/utils/LevelType.java
Normal file
@@ -0,0 +1,16 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Getter
|
||||
public enum LevelType {
|
||||
DEFAULT_TYPE("default"),
|
||||
FLAT("flat"),
|
||||
LARGE_BIOMES("largeBiomes"),
|
||||
AMPLIFIED("amplified"),
|
||||
DEFAULT_1_1("default_1_1");
|
||||
|
||||
private final String type;
|
||||
}
|
||||
18
protocol/src/main/java/mc/protocol/utils/MainHand.java
Normal file
18
protocol/src/main/java/mc/protocol/utils/MainHand.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public enum MainHand {
|
||||
LEFT,
|
||||
RIGHT;
|
||||
|
||||
@Nullable
|
||||
public static MainHand valueById(int id) {
|
||||
// а зачем усложнять?
|
||||
//@formatter:off
|
||||
if (id == 0) return LEFT;
|
||||
else if (id == 1) return RIGHT;
|
||||
else return null;
|
||||
//@formatter:on
|
||||
}
|
||||
}
|
||||
23
protocol/src/main/java/mc/protocol/utils/Table.java
Normal file
23
protocol/src/main/java/mc/protocol/utils/Table.java
Normal file
@@ -0,0 +1,23 @@
|
||||
package mc.protocol.utils;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class Table<C, R, V> {
|
||||
|
||||
private final Map<C, Map<R, V>> map = new HashMap<>();
|
||||
|
||||
@Nullable
|
||||
public V getColumnAndRow(C column, R row) {
|
||||
if (!map.containsKey(column)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return map.get(column).get(row);
|
||||
}
|
||||
|
||||
public void put(C column, R row, V value) {
|
||||
map.computeIfAbsent(column, c -> new HashMap<>()).put(row, value);
|
||||
}
|
||||
}
|
||||
7
protocol/src/main/java/mc/protocol/world/Chunk.java
Normal file
7
protocol/src/main/java/mc/protocol/world/Chunk.java
Normal file
@@ -0,0 +1,7 @@
|
||||
package mc.protocol.world;
|
||||
|
||||
public interface Chunk {
|
||||
|
||||
int getX();
|
||||
int getZ();
|
||||
}
|
||||
13
protocol/src/main/java/mc/protocol/world/World.java
Normal file
13
protocol/src/main/java/mc/protocol/world/World.java
Normal file
@@ -0,0 +1,13 @@
|
||||
package mc.protocol.world;
|
||||
|
||||
import mc.protocol.model.Location;
|
||||
import mc.protocol.utils.LevelType;
|
||||
|
||||
public interface World {
|
||||
|
||||
LevelType getLevelType();
|
||||
|
||||
Location getSpawn();
|
||||
|
||||
Chunk getChunk(int x, int z);
|
||||
}
|
||||
@@ -0,0 +1,305 @@
|
||||
package mc.protocol.buffer;
|
||||
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class NetByteBufReadTest {
|
||||
|
||||
private static Random random;
|
||||
private ByteArrayOutputStream baos;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
random = new Random(System.currentTimeMillis());
|
||||
baos = new ByteArrayOutputStream();
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsReadBoolean")
|
||||
void readBoolean(byte sourceByte, boolean expectedValue) {
|
||||
baos.write(sourceByte);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(expectedValue, netByteBuf.readBoolean());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readByte() {
|
||||
byte[] bytes = new byte[1];
|
||||
random.nextBytes(bytes);
|
||||
baos.write(bytes[0]);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(bytes[0], netByteBuf.readByte());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsReadUnsignedByte")
|
||||
void readUnsignedByte(byte sourceByte, int expectedValue) {
|
||||
baos.write(sourceByte);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(expectedValue, netByteBuf.readUnsignedByte());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readShort() throws IOException {
|
||||
int value = Integer.valueOf(random.nextInt()).shortValue();
|
||||
new DataOutputStream(baos).writeShort(value);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(value, netByteBuf.readShort());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readUnsignedShort() throws IOException {
|
||||
int value = 32768;
|
||||
new DataOutputStream(baos).writeShort(value);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(value, netByteBuf.readUnsignedShort());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsReadString")
|
||||
void readString(String string) throws IOException {
|
||||
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] bytes = new byte[strBytes.length + 1];
|
||||
bytes[0] = (byte) string.codePoints().count(); // допустим, что размер поместился в один байт
|
||||
System.arraycopy(strBytes, 0, bytes, 1, strBytes.length);
|
||||
|
||||
baos.write(bytes);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(string, netByteBuf.readString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readString_overSize() throws IOException {
|
||||
String string = "123";
|
||||
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] bytes = new byte[strBytes.length + 1];
|
||||
final int length = string.length();
|
||||
bytes[0] = (byte) (length + 1); // допустим, что размер поместился в один байт
|
||||
System.arraycopy(strBytes, 0, bytes, 1, strBytes.length);
|
||||
|
||||
baos.write(bytes);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertThrows(NetIOException.class, () -> netByteBuf.readString(length));
|
||||
}
|
||||
|
||||
@Test
|
||||
void readString_lessZero() throws IOException {
|
||||
String string = "123";
|
||||
final byte[] strBytes = string.getBytes(StandardCharsets.UTF_8);
|
||||
final byte[] bytes = new byte[strBytes.length + 5];
|
||||
bytes[0] = (byte) 0xFF;
|
||||
bytes[1] = (byte) 0xFF;
|
||||
bytes[2] = (byte) 0xFF;
|
||||
bytes[3] = (byte) 0xFF;
|
||||
bytes[4] = (byte) 0x0F;
|
||||
System.arraycopy(strBytes, 0, bytes, 5, strBytes.length);
|
||||
|
||||
baos.write(bytes);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertThrows(NetIOException.class, () -> netByteBuf.readString(-1));
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsReadVarInt")
|
||||
void readVarInt(byte[] sourceBytes, int expectedValue) throws IOException {
|
||||
baos.write(sourceBytes);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(expectedValue, netByteBuf.readVarInt());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readVarInt_tooBig() throws IOException {
|
||||
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x0F });
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(-1, netByteBuf.readVarInt());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource({"paramsReadVarInt", "paramsReadVarLong"})
|
||||
void readVarLong(byte[] sourceBytes, long expectedValue) throws IOException {
|
||||
baos.write(sourceBytes);
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(expectedValue, netByteBuf.readVarLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readVarLong_tooBig() throws IOException {
|
||||
baos.write(new byte[]{ (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF,
|
||||
(byte) 0xFF, (byte) 0x0F });
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(-1, netByteBuf.readVarLong());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readUUID() throws IOException {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||
|
||||
baos.write(new byte[]{
|
||||
(byte) ((mostSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (mostSignificantBits & 0xFF)
|
||||
});
|
||||
baos.write(new byte[]{
|
||||
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (leastSignificantBits & 0xFF)
|
||||
});
|
||||
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(uuid, netByteBuf.readUUID());
|
||||
}
|
||||
|
||||
@Test
|
||||
void readBytes() throws IOException {
|
||||
byte[] bytes = new byte[128];
|
||||
random.nextBytes(bytes);
|
||||
baos.write(bytes);
|
||||
|
||||
byte[] actualBytes = new byte[128];
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
|
||||
assertEquals(bytes.length, netByteBuf.readableBytes());
|
||||
|
||||
netByteBuf.readBytes(actualBytes);
|
||||
|
||||
assertArrayEquals(bytes, actualBytes);
|
||||
assertEquals(0, netByteBuf.readableBytes());
|
||||
}
|
||||
|
||||
@Test
|
||||
void read_offset() throws IOException {
|
||||
byte[] bytes = new byte[128];
|
||||
random.nextBytes(bytes);
|
||||
baos.write(bytes);
|
||||
|
||||
byte[] buff = new byte[128];
|
||||
NetByteBuf netByteBuf = new NetByteBuf(Unpooled.wrappedBuffer(baos.toByteArray()));
|
||||
netByteBuf.readBytes(buff, 3, 11);
|
||||
|
||||
byte[] expectedBytes = new byte[11];
|
||||
System.arraycopy(bytes, 0, expectedBytes, 0, 11);
|
||||
byte[] actualBytes = new byte[11];
|
||||
System.arraycopy(buff, 3, actualBytes, 0, 11);
|
||||
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsReadBoolean() {
|
||||
return Stream.of(
|
||||
Arguments.of((byte) 0x00, false),
|
||||
Arguments.of((byte) 0x01, true)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsReadUnsignedByte() {
|
||||
return Stream.of(
|
||||
Arguments.of((byte) 30, 30),
|
||||
Arguments.of((byte) (0xFF & 130), 130)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsReadString() {
|
||||
return Stream.of(
|
||||
Arguments.of(""),
|
||||
Arguments.of("Latin"),
|
||||
Arguments.of("Кириллица"),
|
||||
Arguments.of("العربية"),
|
||||
Arguments.of("ﬦﬣﬡ"), // Алфавитные формы представления
|
||||
Arguments.of("\uD800\uDD07") // Эгейские цифры, [один]
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsReadVarInt() {
|
||||
return Stream.of(
|
||||
Arguments.of(new byte[]{ 0x78 }, 120),
|
||||
Arguments.of(new byte[]{ (byte) 0xE0, 0x5D }, 12000),
|
||||
Arguments.of(new byte[]{ (byte) 0xC0, (byte) 0xA9, 0x07 }, 120000),
|
||||
Arguments.of(new byte[]{ (byte) 0x80, (byte) 0x9C, (byte) 0x9C, (byte) 0x39 }, 120_000_000),
|
||||
Arguments.of(new byte[]{ (byte) 0x80, (byte) 0x98, (byte) 0x9A, (byte) 0xBC, 0x04 }, 1_200_000_000)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsReadVarLong() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0xF0, (byte) 0x85, (byte) 0xDA, 0x2C },
|
||||
12_000_000_000L),
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0xE0, (byte) 0xBA, (byte) 0x84, (byte) 0xBF, 0x03 },
|
||||
120_000_000_000L),
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xF3, (byte) 0xBD, (byte) 0x9F, (byte) 0xDD,
|
||||
0x02 },
|
||||
12_000_000_000_000L),
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xEC, (byte) 0xAD, (byte) 0xCC, (byte) 0xEC,
|
||||
(byte) 0x90, 0x02},
|
||||
1_200_000_000_000_000L),
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xB0, (byte) 0xE8, (byte) 0xD3, (byte) 0xEB,
|
||||
(byte) 0x94, (byte) 0xD5, 0x01 },
|
||||
120_000_000_000_000_000L),
|
||||
Arguments.of(
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||
(byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01 },
|
||||
Long.MIN_VALUE)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,260 @@
|
||||
package mc.protocol.buffer;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.params.ParameterizedTest;
|
||||
import org.junit.jupiter.params.provider.Arguments;
|
||||
import org.junit.jupiter.params.provider.MethodSource;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class NetByteBufWriteTest {
|
||||
|
||||
private static Random random;
|
||||
|
||||
@BeforeAll
|
||||
static void setUp() {
|
||||
random = new Random(System.currentTimeMillis());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsWriteBoolean")
|
||||
void writeBoolean(boolean sourceValue, byte expectedByte) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeBoolean(sourceValue);
|
||||
|
||||
assertEquals(expectedByte, byteBuf.array()[0]);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsWriteByte")
|
||||
void writeByte(byte sourceValue, byte expectedByte) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeByte(sourceValue);
|
||||
|
||||
assertEquals(expectedByte, byteBuf.array()[0]);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeUnsignedByte() {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeUnsignedByte(129);
|
||||
|
||||
assertEquals(129, netByteBuf.readUnsignedByte());
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsWriteString")
|
||||
void writeString(String string, int exceptedLength) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeString(string);
|
||||
|
||||
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||
int actualLength = actualArray[0]; // допустим, что размер поместился в один байт
|
||||
assertEquals(exceptedLength, actualLength);
|
||||
|
||||
byte[] dataBytes = new byte[actualArray.length - 1];
|
||||
System.arraycopy(actualArray, 1, dataBytes, 0, dataBytes.length);
|
||||
assertEquals(string, new String(dataBytes, StandardCharsets.UTF_8));
|
||||
}
|
||||
|
||||
//возможно этот тест нужно перенести в NetByteBufReadTest
|
||||
@Test
|
||||
void writeString_overSize() {
|
||||
String overSizeString = RandomStringUtils.randomAscii(Short.MAX_VALUE + Short.MAX_VALUE);
|
||||
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeString(overSizeString);
|
||||
|
||||
NetByteBuf netByteBuf2 = new NetByteBuf(byteBuf.copy());
|
||||
String actualString = netByteBuf2.readString();
|
||||
|
||||
String expectedString = overSizeString.substring(0, Short.MAX_VALUE);
|
||||
|
||||
assertEquals(expectedString, actualString);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("paramsWriteVarInt")
|
||||
void writeVarInt(int sourceValue, byte[] expectedBytes) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeVarInt(sourceValue);
|
||||
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||
|
||||
assertArrayEquals(expectedBytes, actualArray);
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource({ "paramsWriteVarInt", "paramsWriteVarLong" })
|
||||
void writeVarLong(long sourceValue, byte[] expectedBytes) {
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeVarLong(sourceValue);
|
||||
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||
|
||||
assertArrayEquals(expectedBytes, actualArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeUUID() {
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeUUID(uuid);
|
||||
|
||||
final long mostSignificantBits = uuid.getMostSignificantBits();
|
||||
final long leastSignificantBits = uuid.getLeastSignificantBits();
|
||||
|
||||
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||
|
||||
assertArrayEquals(new byte[]{
|
||||
(byte) ((mostSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((mostSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (mostSignificantBits & 0xFF),
|
||||
|
||||
(byte) ((leastSignificantBits >>> 56) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 48) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 40) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 32) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 24) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 16) & 0xFF),
|
||||
(byte) ((leastSignificantBits >>> 8) & 0xFF),
|
||||
(byte) (leastSignificantBits & 0xFF) },
|
||||
actualArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
void writeBytes() {
|
||||
byte[] bytes = new byte[128];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeBytes(bytes);
|
||||
byte[] actualArray = netByteBuf.copy(0, netByteBuf.readableBytes()).array();
|
||||
|
||||
assertArrayEquals(bytes, actualArray);
|
||||
}
|
||||
|
||||
@Test
|
||||
void write_offset() {
|
||||
byte[] bytes = new byte[128];
|
||||
random.nextBytes(bytes);
|
||||
|
||||
ByteBuf byteBuf = Unpooled.buffer();
|
||||
NetByteBuf netByteBuf = new NetByteBuf(byteBuf);
|
||||
|
||||
netByteBuf.writeBytes(bytes, 3, 11);
|
||||
|
||||
byte[] actualBytes = new byte[11];
|
||||
System.arraycopy(byteBuf.array(), 0, actualBytes, 0, 11);
|
||||
|
||||
byte[] expectedBytes = new byte[11];
|
||||
System.arraycopy(bytes, 3, expectedBytes, 0, 11);
|
||||
|
||||
assertArrayEquals(expectedBytes, actualBytes);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsWriteBoolean() {
|
||||
return Stream.of(
|
||||
Arguments.of(false, (byte) 0x00),
|
||||
Arguments.of(true, (byte) 0x01)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsWriteByte() {
|
||||
byte b = Integer.valueOf(random.nextInt()).byteValue();
|
||||
|
||||
return Stream.of(
|
||||
Arguments.of(b, b),
|
||||
Arguments.of((byte) 128, (byte) -128)
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsWriteString() {
|
||||
return Stream.of(
|
||||
Arguments.of("", 0),
|
||||
Arguments.of("Latin", 5),
|
||||
Arguments.of("Кириллица", 37),
|
||||
// (9) -> "Кириллица"(18) => 18*2=36 (37?)
|
||||
Arguments.of("العربية", 30),
|
||||
// (7) -> "Ш§Щ„Ш№Ш±ШЁЩЉШ©"(14) => 14*2=28 (30?)
|
||||
Arguments.of("ﬦﬣﬡ", 18), // Алфавитные формы представления
|
||||
// (3) -> "ﬦﬣﬡ"(9) => 9*2=18
|
||||
Arguments.of("\uD800\uDD07", 4) // Эгейские цифры, [один]
|
||||
// (1) -> "𐄇" => ...4!
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsWriteVarInt() {
|
||||
return Stream.of(
|
||||
Arguments.of(120, new byte[]{ 0x78 }),
|
||||
Arguments.of(12000, new byte[]{ (byte) 0xE0, 0x5D }),
|
||||
Arguments.of(120000, new byte[]{ (byte) 0xC0, (byte) 0xA9, 0x07 }),
|
||||
Arguments.of(120000000, new byte[]{ (byte) 0x80, (byte) 0x9C, (byte) 0x9C, (byte) 0x39 }),
|
||||
Arguments.of(1200000000, new byte[]{ (byte) 0x80, (byte) 0x98, (byte) 0x9A, (byte) 0xBC, 0x04 })
|
||||
);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
private static Stream<Arguments> paramsWriteVarLong() {
|
||||
return Stream.of(
|
||||
Arguments.of(
|
||||
12_000_000_000L,
|
||||
new byte[]{ (byte) 0x80, (byte) 0xF0, (byte) 0x85, (byte) 0xDA, 0x2C }),
|
||||
Arguments.of(
|
||||
120_000_000_000L,
|
||||
new byte[]{ (byte) 0x80, (byte) 0xE0, (byte) 0xBA, (byte) 0x84, (byte) 0xBF, 0x03 }),
|
||||
Arguments.of(
|
||||
12_000_000_000_000L,
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xF3, (byte) 0xBD, (byte) 0x9F, (byte) 0xDD,
|
||||
0x02 }),
|
||||
Arguments.of(
|
||||
1_200_000_000_000_000L,
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xEC, (byte) 0xAD, (byte) 0xCC, (byte) 0xEC,
|
||||
(byte) 0x90, 0x02 }),
|
||||
Arguments.of(
|
||||
120_000_000_000_000_000L,
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0xB0, (byte) 0xE8, (byte) 0xD3, (byte) 0xEB,
|
||||
(byte) 0x94, (byte) 0xD5, 0x01 }),
|
||||
Arguments.of(
|
||||
Long.MIN_VALUE,
|
||||
new byte[]{ (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80,
|
||||
(byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01 })
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package mc.protocol.handler.codec;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class ProtocolSplitterTest {
|
||||
|
||||
byte[] expectedPacket1Bytes;
|
||||
byte[] expectedPacket2Bytes;
|
||||
byte[] bytes;
|
||||
|
||||
@Test
|
||||
void split() {
|
||||
prepare();
|
||||
|
||||
ByteBuf inputBuffer = Unpooled.wrappedBuffer(bytes);
|
||||
List<Object> outputObject = new ArrayList<>();
|
||||
|
||||
ProtocolSplitter protocolSplitter = new ProtocolSplitter();
|
||||
protocolSplitter.decode(null, inputBuffer, outputObject);
|
||||
|
||||
assertEquals(2, outputObject.size());
|
||||
assertTrue(outputObject.get(0) instanceof ByteBuf);
|
||||
assertArrayEquals(expectedPacket1Bytes, ((ByteBuf)outputObject.get(0)).array());
|
||||
assertTrue(outputObject.get(1) instanceof ByteBuf);
|
||||
assertArrayEquals(expectedPacket2Bytes, ((ByteBuf)outputObject.get(1)).array());
|
||||
}
|
||||
|
||||
private void prepare() {
|
||||
/*
|
||||
+-------------------------------------------------+
|
||||
| 0 1 2 3 4 5 6 7 8 9 a b c d e f |
|
||||
+--------+-------------------------------------------------+----------------+
|
||||
|00000000| 10 00 d4 02 09 31 32 37 2e 30 2e 30 2e 31 63 dd |.....127.0.0.1c.|
|
||||
|00000010| 01 01 00 |... |
|
||||
+--------+-------------------------------------------------+----------------+
|
||||
*/
|
||||
expectedPacket1Bytes = new byte[]{
|
||||
0x00, (byte) 0xd4, 0x02, 0x09, 0x31, 0x32, 0x37, 0x2e, 0x30, 0x2e, 0x30, 0x2e, 0x31, 0x63,
|
||||
(byte) 0xdd, 0x01
|
||||
};
|
||||
expectedPacket2Bytes = new byte[]{
|
||||
0x00
|
||||
};
|
||||
bytes = new byte[2 + expectedPacket1Bytes.length + expectedPacket2Bytes.length];
|
||||
bytes[0] = 0x10;
|
||||
System.arraycopy(expectedPacket1Bytes, 0,
|
||||
bytes, 1, expectedPacket1Bytes.length);
|
||||
bytes[1 + expectedPacket1Bytes.length] = 0x01;
|
||||
System.arraycopy(expectedPacket2Bytes, 0,
|
||||
bytes, 2 + expectedPacket1Bytes.length, expectedPacket2Bytes.length);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user