mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-26 21:52:50 +02:00
Buffer API rework (#1485)
This commit is contained in:
parent
c3df2af306
commit
ba2816fc74
@ -142,7 +142,7 @@ public class BasicQueryResponse implements Writeable {
|
|||||||
writer.writeNullTerminatedString(this.map, Query.CHARSET);
|
writer.writeNullTerminatedString(this.map, Query.CHARSET);
|
||||||
writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET);
|
writer.writeNullTerminatedString(this.numPlayers, Query.CHARSET);
|
||||||
writer.writeNullTerminatedString(this.maxPlayers, Query.CHARSET);
|
writer.writeNullTerminatedString(this.maxPlayers, Query.CHARSET);
|
||||||
writer.getBuffer().putShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
|
writer.writeShort((short) MinecraftServer.getServer().getPort()); // TODO little endian?
|
||||||
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
|
writer.writeNullTerminatedString(Objects.requireNonNullElse(MinecraftServer.getServer().getAddress(), ""), Query.CHARSET);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,17 @@
|
|||||||
package net.minestom.server.extras.velocity;
|
package net.minestom.server.extras.velocity;
|
||||||
|
|
||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.utils.binary.BinaryReader;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import javax.crypto.Mac;
|
import javax.crypto.Mac;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.UnknownHostException;
|
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
|
||||||
|
import static net.minestom.server.network.NetworkBuffer.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Support for <a href="https://velocitypowered.com/">Velocity</a> modern forwarding.
|
* Support for <a href="https://velocitypowered.com/">Velocity</a> modern forwarding.
|
||||||
* <p>
|
* <p>
|
||||||
@ -47,12 +45,14 @@ public final class VelocityProxy {
|
|||||||
return enabled;
|
return enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean checkIntegrity(@NotNull BinaryReader reader) {
|
public static boolean checkIntegrity(NetworkBuffer buffer) {
|
||||||
final byte[] signature = reader.readBytes(32);
|
final byte[] signature = new byte[32];
|
||||||
ByteBuffer buf = reader.getBuffer();
|
for (int i = 0; i < signature.length; i++) {
|
||||||
buf.mark();
|
signature[i] = buffer.read(BYTE);
|
||||||
final byte[] data = reader.readRemainingBytes();
|
}
|
||||||
buf.reset();
|
final int index = buffer.readIndex();
|
||||||
|
final byte[] data = buffer.read(RAW_BYTES);
|
||||||
|
buffer.readIndex(index);
|
||||||
try {
|
try {
|
||||||
Mac mac = Mac.getInstance(MAC_ALGORITHM);
|
Mac mac = Mac.getInstance(MAC_ALGORITHM);
|
||||||
mac.init(key);
|
mac.init(key);
|
||||||
@ -63,16 +63,7 @@ public final class VelocityProxy {
|
|||||||
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
|
||||||
e.printStackTrace();
|
e.printStackTrace();
|
||||||
}
|
}
|
||||||
final int version = reader.readVarInt();
|
final int version = buffer.read(VAR_INT);
|
||||||
return version == SUPPORTED_FORWARDING_VERSION;
|
return version == SUPPORTED_FORWARDING_VERSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static InetAddress readAddress(@NotNull BinaryReader reader) {
|
|
||||||
try {
|
|
||||||
return InetAddress.getByName(reader.readSizedString());
|
|
||||||
} catch (UnknownHostException e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,6 @@ import net.minestom.server.snapshot.SnapshotUpdater;
|
|||||||
import net.minestom.server.utils.ArrayUtils;
|
import net.minestom.server.utils.ArrayUtils;
|
||||||
import net.minestom.server.utils.MathUtils;
|
import net.minestom.server.utils.MathUtils;
|
||||||
import net.minestom.server.utils.ObjectPool;
|
import net.minestom.server.utils.ObjectPool;
|
||||||
import net.minestom.server.utils.Utils;
|
|
||||||
import net.minestom.server.utils.binary.BinaryWriter;
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
import net.minestom.server.utils.chunk.ChunkUtils;
|
import net.minestom.server.utils.chunk.ChunkUtils;
|
||||||
import net.minestom.server.world.biomes.Biome;
|
import net.minestom.server.world.biomes.Biome;
|
||||||
@ -200,8 +199,8 @@ public class DynamicChunk extends Chunk {
|
|||||||
}
|
}
|
||||||
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
final int bitsForHeight = MathUtils.bitsToRepresent(dimensionHeight);
|
||||||
heightmapsNBT = NBT.Compound(Map.of(
|
heightmapsNBT = NBT.Compound(Map.of(
|
||||||
"MOTION_BLOCKING", NBT.LongArray(Utils.encodeBlocks(motionBlocking, bitsForHeight)),
|
"MOTION_BLOCKING", NBT.LongArray(encodeBlocks(motionBlocking, bitsForHeight)),
|
||||||
"WORLD_SURFACE", NBT.LongArray(Utils.encodeBlocks(worldSurface, bitsForHeight))));
|
"WORLD_SURFACE", NBT.LongArray(encodeBlocks(worldSurface, bitsForHeight))));
|
||||||
}
|
}
|
||||||
// Data
|
// Data
|
||||||
final byte[] data = ObjectPool.PACKET_POOL.use(buffer -> {
|
final byte[] data = ObjectPool.PACKET_POOL.use(buffer -> {
|
||||||
@ -265,4 +264,47 @@ public class DynamicChunk extends Chunk {
|
|||||||
private void assertLock() {
|
private void assertLock() {
|
||||||
assert Thread.holdsLock(this) : "Chunk must be locked before access";
|
assert Thread.holdsLock(this) : "Chunk must be locked before access";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final int[] MAGIC = {
|
||||||
|
-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
|
||||||
|
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
|
||||||
|
0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
|
||||||
|
390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
|
||||||
|
306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135,
|
||||||
|
0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
|
||||||
|
204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
|
||||||
|
178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
|
||||||
|
0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
|
||||||
|
138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
|
||||||
|
126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
|
||||||
|
0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
|
||||||
|
104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
|
||||||
|
97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
|
||||||
|
0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
|
||||||
|
84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
|
||||||
|
79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
|
||||||
|
0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
|
||||||
|
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
||||||
|
0, 5};
|
||||||
|
|
||||||
|
private static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
|
||||||
|
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
||||||
|
final char valuesPerLong = (char) (64 / bitsPerEntry);
|
||||||
|
final int magicIndex = 3 * (valuesPerLong - 1);
|
||||||
|
final long divideMul = Integer.toUnsignedLong(MAGIC[magicIndex]);
|
||||||
|
final long divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1]);
|
||||||
|
final int divideShift = MAGIC[magicIndex + 2];
|
||||||
|
final int size = (blocks.length + valuesPerLong - 1) / valuesPerLong;
|
||||||
|
|
||||||
|
long[] data = new long[size];
|
||||||
|
|
||||||
|
for (int i = 0; i < blocks.length; i++) {
|
||||||
|
final long value = blocks[i];
|
||||||
|
final int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
|
||||||
|
final int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
|
||||||
|
data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -42,10 +42,7 @@ record ItemMetaImpl(TagHandler tagHandler) implements ItemMeta {
|
|||||||
writer.writeByte((byte) 0);
|
writer.writeByte((byte) 0);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
BinaryWriter w = new BinaryWriter();
|
writer.writeNBT("", nbt);
|
||||||
w.writeNBT("", nbt);
|
|
||||||
var cachedBuffer = w.getBuffer();
|
|
||||||
writer.write(cachedBuffer.flip());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
package net.minestom.server.item;
|
package net.minestom.server.item;
|
||||||
|
|
||||||
|
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.event.HoverEvent;
|
import net.kyori.adventure.text.event.HoverEvent;
|
||||||
import net.kyori.adventure.text.event.HoverEventSource;
|
import net.kyori.adventure.text.event.HoverEventSource;
|
||||||
|
import net.minestom.server.adventure.MinestomAdventure;
|
||||||
import net.minestom.server.tag.Tag;
|
import net.minestom.server.tag.Tag;
|
||||||
import net.minestom.server.tag.TagHandler;
|
import net.minestom.server.tag.TagHandler;
|
||||||
import net.minestom.server.tag.TagReadable;
|
import net.minestom.server.tag.TagReadable;
|
||||||
import net.minestom.server.tag.TagWritable;
|
import net.minestom.server.tag.TagWritable;
|
||||||
import net.minestom.server.utils.NBTUtils;
|
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.*;
|
import org.jetbrains.annotations.*;
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
import org.jglrxavpok.hephaistos.nbt.NBTCompound;
|
||||||
@ -167,8 +168,8 @@ public sealed interface ItemStack extends TagReadable, HoverEventSource<HoverEve
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
|
default @NotNull HoverEvent<HoverEvent.ShowItem> asHoverEvent(@NotNull UnaryOperator<HoverEvent.ShowItem> op) {
|
||||||
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(),
|
final BinaryTagHolder tagHolder = BinaryTagHolder.encode(meta().toNBT(), MinestomAdventure.NBT_CODEC);
|
||||||
NBTUtils.asBinaryTagHolder(meta().toNBT()))));
|
return HoverEvent.showItem(op.apply(HoverEvent.ShowItem.of(material(), amount(), tagHolder)));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
158
src/main/java/net/minestom/server/network/NetworkBuffer.java
Normal file
158
src/main/java/net/minestom/server/network/NetworkBuffer.java
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
package net.minestom.server.network;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBTReader;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
|
||||||
|
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@ApiStatus.Experimental
|
||||||
|
public final class NetworkBuffer {
|
||||||
|
public static final Type<Boolean> BOOLEAN = NetworkBufferTypes.BOOLEAN;
|
||||||
|
public static final Type<Byte> BYTE = NetworkBufferTypes.BYTE;
|
||||||
|
public static final Type<Short> SHORT = NetworkBufferTypes.SHORT;
|
||||||
|
public static final Type<Integer> INT = NetworkBufferTypes.INT;
|
||||||
|
public static final Type<Long> LONG = NetworkBufferTypes.LONG;
|
||||||
|
public static final Type<Float> FLOAT = NetworkBufferTypes.FLOAT;
|
||||||
|
public static final Type<Double> DOUBLE = NetworkBufferTypes.DOUBLE;
|
||||||
|
public static final Type<Integer> VAR_INT = NetworkBufferTypes.VAR_INT;
|
||||||
|
public static final Type<Long> VAR_LONG = NetworkBufferTypes.VAR_LONG;
|
||||||
|
public static final Type<byte[]> RAW_BYTES = NetworkBufferTypes.RAW_BYTES;
|
||||||
|
public static final Type<String> STRING = NetworkBufferTypes.STRING;
|
||||||
|
public static final Type<NBT> NBT = NetworkBufferTypes.NBT;
|
||||||
|
public static final Type<Point> BLOCK_POSITION = NetworkBufferTypes.BLOCK_POSITION;
|
||||||
|
public static final Type<Component> COMPONENT = NetworkBufferTypes.COMPONENT;
|
||||||
|
public static final Type<UUID> UUID = NetworkBufferTypes.UUID;
|
||||||
|
public static final Type<ItemStack> ITEM = NetworkBufferTypes.ITEM;
|
||||||
|
|
||||||
|
ByteBuffer nioBuffer;
|
||||||
|
final boolean resizable;
|
||||||
|
int writeIndex;
|
||||||
|
int readIndex;
|
||||||
|
|
||||||
|
NBTWriter nbtWriter;
|
||||||
|
NBTReader nbtReader;
|
||||||
|
|
||||||
|
public NetworkBuffer(@NotNull ByteBuffer buffer, boolean resizable) {
|
||||||
|
this.nioBuffer = buffer.order(ByteOrder.BIG_ENDIAN);
|
||||||
|
this.resizable = resizable;
|
||||||
|
|
||||||
|
this.writeIndex = buffer.position();
|
||||||
|
this.readIndex = buffer.position();
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkBuffer(@NotNull ByteBuffer buffer) {
|
||||||
|
this(buffer, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkBuffer(int initialCapacity) {
|
||||||
|
this(ByteBuffer.allocateDirect(initialCapacity), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public NetworkBuffer() {
|
||||||
|
this(1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void write(@NotNull Type<T> type, @NotNull T value) {
|
||||||
|
var impl = (NetworkBufferTypes.TypeImpl<T>) type;
|
||||||
|
final long length = impl.writer().write(this, value);
|
||||||
|
if (length != -1) this.writeIndex += length;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> @NotNull T read(@NotNull Type<T> type) {
|
||||||
|
var impl = (NetworkBufferTypes.TypeImpl<T>) type;
|
||||||
|
return impl.reader().read(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void writeOptional(@NotNull Type<T> type, @Nullable T value) {
|
||||||
|
write(BOOLEAN, value != null);
|
||||||
|
if (value != null) write(type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> @Nullable T readOptional(@NotNull Type<T> type) {
|
||||||
|
return read(BOOLEAN) ? read(type) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> void writeCollection(@NotNull Type<T> type, @Nullable Collection<@NotNull T> values) {
|
||||||
|
if (values == null) {
|
||||||
|
write(BYTE, (byte) 0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
write(VAR_INT, values.size());
|
||||||
|
for (T value : values) {
|
||||||
|
write(type, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SafeVarargs
|
||||||
|
public final <T> void writeCollection(@NotNull Type<T> type, @NotNull T @Nullable ... values) {
|
||||||
|
writeCollection(type, values == null ? null : List.of(values));
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> @NotNull Collection<@NotNull T> readCollection(@NotNull Type<T> type) {
|
||||||
|
final int size = read(VAR_INT);
|
||||||
|
final Collection<T> values = new java.util.ArrayList<>(size);
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
values.add(read(type));
|
||||||
|
}
|
||||||
|
return values;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void copyTo(int srcOffset, byte @NotNull [] dest, int destOffset, int length) {
|
||||||
|
this.nioBuffer.get(srcOffset, dest, destOffset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void clear() {
|
||||||
|
this.writeIndex = 0;
|
||||||
|
this.readIndex = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int writeIndex() {
|
||||||
|
return writeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readIndex() {
|
||||||
|
return readIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void writeIndex(int writeIndex) {
|
||||||
|
this.writeIndex = writeIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void readIndex(int readIndex) {
|
||||||
|
this.readIndex = readIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int skipWrite(int length) {
|
||||||
|
final int oldWriteIndex = writeIndex;
|
||||||
|
writeIndex += length;
|
||||||
|
return oldWriteIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readableBytes() {
|
||||||
|
return writeIndex - readIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ensureSize(int length) {
|
||||||
|
if (!resizable) return;
|
||||||
|
if (nioBuffer.capacity() < writeIndex + length) {
|
||||||
|
ByteBuffer newBuffer = ByteBuffer.allocateDirect(nioBuffer.capacity() * 2);
|
||||||
|
nioBuffer.position(0);
|
||||||
|
newBuffer.put(nioBuffer);
|
||||||
|
nioBuffer = newBuffer.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public sealed interface Type<T> permits NetworkBufferTypes.TypeImpl {
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,322 @@
|
|||||||
|
package net.minestom.server.network;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
final class NetworkBufferTypes {
|
||||||
|
private static final int SEGMENT_BITS = 0x7F;
|
||||||
|
private static final int CONTINUE_BIT = 0x80;
|
||||||
|
|
||||||
|
static final TypeImpl<Boolean> BOOLEAN = new TypeImpl<>(Boolean.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(1);
|
||||||
|
buffer.nioBuffer.put(buffer.writeIndex(), value ? (byte) 1 : (byte) 0);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final byte value = buffer.nioBuffer.get(buffer.readIndex());
|
||||||
|
buffer.readIndex += 1;
|
||||||
|
return value == 1;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Byte> BYTE = new TypeImpl<>(Byte.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(1);
|
||||||
|
buffer.nioBuffer.put(buffer.writeIndex(), value);
|
||||||
|
return 1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final byte value = buffer.nioBuffer.get(buffer.readIndex());
|
||||||
|
buffer.readIndex += 1;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Short> SHORT = new TypeImpl<>(Short.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(2);
|
||||||
|
buffer.nioBuffer.putShort(buffer.writeIndex(), value);
|
||||||
|
return 2;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final short value = buffer.nioBuffer.getShort(buffer.readIndex());
|
||||||
|
buffer.readIndex += 2;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Integer> INT = new TypeImpl<>(Integer.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(4);
|
||||||
|
buffer.nioBuffer.putInt(buffer.writeIndex(), value);
|
||||||
|
return 4;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final int value = buffer.nioBuffer.getInt(buffer.readIndex());
|
||||||
|
buffer.readIndex += 4;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Long> LONG = new TypeImpl<>(Long.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(8);
|
||||||
|
buffer.nioBuffer.putLong(buffer.writeIndex(), value);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final long value = buffer.nioBuffer.getLong(buffer.readIndex());
|
||||||
|
buffer.readIndex += 8;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Float> FLOAT = new TypeImpl<>(Float.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(4);
|
||||||
|
buffer.nioBuffer.putFloat(buffer.writeIndex(), value);
|
||||||
|
return 4;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final float value = buffer.nioBuffer.getFloat(buffer.readIndex());
|
||||||
|
buffer.readIndex += 4;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Double> DOUBLE = new TypeImpl<>(Double.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(8);
|
||||||
|
buffer.nioBuffer.putDouble(buffer.writeIndex(), value);
|
||||||
|
return 8;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final double value = buffer.nioBuffer.getDouble(buffer.readIndex());
|
||||||
|
buffer.readIndex += 8;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<Integer> VAR_INT = new TypeImpl<>(Integer.class,
|
||||||
|
(buffer, boxed) -> {
|
||||||
|
final int value = boxed;
|
||||||
|
final int index = buffer.writeIndex();
|
||||||
|
var nio = buffer.nioBuffer;
|
||||||
|
if ((value & (0xFFFFFFFF << 7)) == 0) {
|
||||||
|
buffer.ensureSize(1);
|
||||||
|
nio.put(index, (byte) value);
|
||||||
|
return 1;
|
||||||
|
} else if ((value & (0xFFFFFFFF << 14)) == 0) {
|
||||||
|
buffer.ensureSize(2);
|
||||||
|
nio.putShort(index, (short) ((value & 0x7F | 0x80) << 8 | (value >>> 7)));
|
||||||
|
return 2;
|
||||||
|
} else if ((value & (0xFFFFFFFF << 21)) == 0) {
|
||||||
|
buffer.ensureSize(3);
|
||||||
|
nio.put(index, (byte) (value & 0x7F | 0x80));
|
||||||
|
nio.put(index + 1, (byte) ((value >>> 7) & 0x7F | 0x80));
|
||||||
|
nio.put(index + 2, (byte) (value >>> 14));
|
||||||
|
return 3;
|
||||||
|
} else if ((value & (0xFFFFFFFF << 28)) == 0) {
|
||||||
|
buffer.ensureSize(4);
|
||||||
|
nio.putInt(index, (value & 0x7F | 0x80) << 24 | (((value >>> 7) & 0x7F | 0x80) << 16)
|
||||||
|
| ((value >>> 14) & 0x7F | 0x80) << 8 | (value >>> 21));
|
||||||
|
return 4;
|
||||||
|
} else {
|
||||||
|
buffer.ensureSize(5);
|
||||||
|
nio.putInt(index, (value & 0x7F | 0x80) << 24 | ((value >>> 7) & 0x7F | 0x80) << 16
|
||||||
|
| ((value >>> 14) & 0x7F | 0x80) << 8 | ((value >>> 21) & 0x7F | 0x80));
|
||||||
|
nio.put(index + 4, (byte) (value >>> 28));
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
int index = buffer.readIndex();
|
||||||
|
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
|
||||||
|
int result = 0;
|
||||||
|
for (int shift = 0; ; shift += 7) {
|
||||||
|
byte b = buffer.nioBuffer.get(index++);
|
||||||
|
result |= (b & 0x7f) << shift;
|
||||||
|
if (b >= 0) {
|
||||||
|
buffer.readIndex += index - buffer.readIndex();
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
static final TypeImpl<Long> VAR_LONG = new TypeImpl<>(Long.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(10);
|
||||||
|
int size = 0;
|
||||||
|
while (true) {
|
||||||
|
if ((value & ~((long) SEGMENT_BITS)) == 0) {
|
||||||
|
buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) value.intValue());
|
||||||
|
return size + 1;
|
||||||
|
}
|
||||||
|
buffer.nioBuffer.put(buffer.writeIndex() + size, (byte) (value & SEGMENT_BITS | CONTINUE_BIT));
|
||||||
|
size++;
|
||||||
|
// Note: >>> means that the sign bit is shifted with the rest of the number rather than being left alone
|
||||||
|
value >>>= 7;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
int length = 0;
|
||||||
|
|
||||||
|
long value = 0;
|
||||||
|
int position = 0;
|
||||||
|
byte currentByte;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
currentByte = buffer.nioBuffer.get(buffer.readIndex() + length);
|
||||||
|
length++;
|
||||||
|
value |= (long) (currentByte & SEGMENT_BITS) << position;
|
||||||
|
if ((currentByte & CONTINUE_BIT) == 0) break;
|
||||||
|
position += 7;
|
||||||
|
if (position >= 64) throw new RuntimeException("VarLong is too big");
|
||||||
|
}
|
||||||
|
buffer.readIndex += length;
|
||||||
|
return value;
|
||||||
|
});
|
||||||
|
static final TypeImpl<byte[]> RAW_BYTES = new TypeImpl<>(byte[].class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.ensureSize(value.length);
|
||||||
|
buffer.nioBuffer.put(buffer.writeIndex(), value);
|
||||||
|
return value.length;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final int limit = buffer.nioBuffer.limit();
|
||||||
|
final int length = limit - buffer.readIndex();
|
||||||
|
assert length > 0 : "Invalid remaining: " + length;
|
||||||
|
final byte[] bytes = new byte[length];
|
||||||
|
buffer.nioBuffer.get(buffer.readIndex(), bytes);
|
||||||
|
buffer.readIndex += length;
|
||||||
|
return bytes;
|
||||||
|
});
|
||||||
|
static final TypeImpl<String> STRING = new TypeImpl<>(String.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
final byte[] bytes = value.getBytes(StandardCharsets.UTF_8);
|
||||||
|
buffer.write(VAR_INT, bytes.length);
|
||||||
|
buffer.write(RAW_BYTES, bytes);
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final int length = buffer.read(VAR_INT);
|
||||||
|
byte[] bytes = new byte[length];
|
||||||
|
buffer.nioBuffer.get(buffer.readIndex(), bytes);
|
||||||
|
buffer.readIndex += length;
|
||||||
|
return new String(bytes, StandardCharsets.UTF_8);
|
||||||
|
});
|
||||||
|
static final TypeImpl<NBT> NBT = new TypeImpl<>(NBT.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
NBTWriter nbtWriter = buffer.nbtWriter;
|
||||||
|
if (nbtWriter == null) {
|
||||||
|
nbtWriter = new NBTWriter(new OutputStream() {
|
||||||
|
@Override
|
||||||
|
public void write(int b) {
|
||||||
|
buffer.write(BYTE, (byte) b);
|
||||||
|
}
|
||||||
|
}, CompressedProcesser.NONE);
|
||||||
|
buffer.nbtWriter = nbtWriter;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
nbtWriter.writeNamed("", value);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
NBTReader nbtReader = buffer.nbtReader;
|
||||||
|
if (nbtReader == null) {
|
||||||
|
nbtReader = new NBTReader(new InputStream() {
|
||||||
|
@Override
|
||||||
|
public int read() {
|
||||||
|
return buffer.read(BYTE);
|
||||||
|
}
|
||||||
|
}, CompressedProcesser.NONE);
|
||||||
|
buffer.nbtReader = nbtReader;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return nbtReader.read();
|
||||||
|
} catch (IOException | NBTException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
static final TypeImpl<Point> BLOCK_POSITION = new TypeImpl<>(Point.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
final int blockX = value.blockX();
|
||||||
|
final int blockY = value.blockY();
|
||||||
|
final int blockZ = value.blockZ();
|
||||||
|
final long longPos = (((long) blockX & 0x3FFFFFF) << 38) |
|
||||||
|
(((long) blockZ & 0x3FFFFFF) << 12) |
|
||||||
|
((long) blockY & 0xFFF);
|
||||||
|
buffer.write(LONG, longPos);
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final long value = buffer.read(LONG);
|
||||||
|
final int x = (int) (value >> 38);
|
||||||
|
final int y = (int) (value << 52 >> 52);
|
||||||
|
final int z = (int) (value << 26 >> 38);
|
||||||
|
return new Vec(x, y, z);
|
||||||
|
});
|
||||||
|
static final TypeImpl<Component> COMPONENT = new TypeImpl<>(Component.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
final String json = GsonComponentSerializer.gson().serialize(value);
|
||||||
|
buffer.write(STRING, json);
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final String json = buffer.read(STRING);
|
||||||
|
return GsonComponentSerializer.gson().deserialize(json);
|
||||||
|
});
|
||||||
|
static final TypeImpl<UUID> UUID = new TypeImpl<>(UUID.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
buffer.write(LONG, value.getMostSignificantBits());
|
||||||
|
buffer.write(LONG, value.getLeastSignificantBits());
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final long mostSignificantBits = buffer.read(LONG);
|
||||||
|
final long leastSignificantBits = buffer.read(LONG);
|
||||||
|
return new UUID(mostSignificantBits, leastSignificantBits);
|
||||||
|
});
|
||||||
|
static final TypeImpl<ItemStack> ITEM = new TypeImpl<>(ItemStack.class,
|
||||||
|
(buffer, value) -> {
|
||||||
|
if (value.isAir()) {
|
||||||
|
buffer.write(BOOLEAN, false);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
buffer.write(BOOLEAN, true);
|
||||||
|
buffer.write(VAR_INT, value.material().id());
|
||||||
|
buffer.write(BYTE, (byte) value.amount());
|
||||||
|
buffer.write(NBT, value.meta().toNBT());
|
||||||
|
return -1;
|
||||||
|
},
|
||||||
|
buffer -> {
|
||||||
|
final boolean present = buffer.read(BOOLEAN);
|
||||||
|
if (!present) return ItemStack.AIR;
|
||||||
|
|
||||||
|
final int id = buffer.read(VAR_INT);
|
||||||
|
final Material material = Material.fromId(id);
|
||||||
|
if (material == null) throw new RuntimeException("Unknown material id: " + id);
|
||||||
|
|
||||||
|
final int amount = buffer.read(BYTE);
|
||||||
|
final NBT nbt = buffer.read(NBT);
|
||||||
|
if (!(nbt instanceof NBTCompound compound)) {
|
||||||
|
return ItemStack.of(material, amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ItemStack.fromNBT(material, compound, amount);
|
||||||
|
});
|
||||||
|
|
||||||
|
record TypeImpl<T>(@NotNull Class<T> type,
|
||||||
|
@NotNull TypeWriter<T> writer,
|
||||||
|
@NotNull TypeReader<T> reader) implements NetworkBuffer.Type<T> {
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeWriter<T> {
|
||||||
|
long write(@NotNull NetworkBuffer buffer, @NotNull T value);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TypeReader<T> {
|
||||||
|
@NotNull T read(@NotNull NetworkBuffer buffer);
|
||||||
|
}
|
||||||
|
}
|
@ -27,16 +27,19 @@ public record PacketProcessor(@NotNull ClientPacketsHandler statusHandler,
|
|||||||
}
|
}
|
||||||
|
|
||||||
public @NotNull ClientPacket create(@NotNull ConnectionState connectionState, int packetId, ByteBuffer body) {
|
public @NotNull ClientPacket create(@NotNull ConnectionState connectionState, int packetId, ByteBuffer body) {
|
||||||
BinaryReader binaryReader = new BinaryReader(body);
|
NetworkBuffer networkBuffer = new NetworkBuffer(body);
|
||||||
return switch (connectionState) {
|
BinaryReader reader = new BinaryReader(networkBuffer);
|
||||||
case PLAY -> playHandler.create(packetId, binaryReader);
|
final ClientPacket clientPacket = switch (connectionState) {
|
||||||
case LOGIN -> loginHandler.create(packetId, binaryReader);
|
case PLAY -> playHandler.create(packetId, reader);
|
||||||
case STATUS -> statusHandler.create(packetId, binaryReader);
|
case LOGIN -> loginHandler.create(packetId, reader);
|
||||||
|
case STATUS -> statusHandler.create(packetId, reader);
|
||||||
case UNKNOWN -> {
|
case UNKNOWN -> {
|
||||||
assert packetId == 0;
|
assert packetId == 0;
|
||||||
yield new HandshakePacket(binaryReader);
|
yield new HandshakePacket(reader);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
body.position(networkBuffer.readIndex());
|
||||||
|
return clientPacket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ClientPacket process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {
|
public ClientPacket process(@NotNull PlayerConnection connection, int packetId, ByteBuffer body) {
|
||||||
|
@ -5,6 +5,7 @@ import net.kyori.adventure.text.format.NamedTextColor;
|
|||||||
import net.minestom.server.MinecraftServer;
|
import net.minestom.server.MinecraftServer;
|
||||||
import net.minestom.server.extras.velocity.VelocityProxy;
|
import net.minestom.server.extras.velocity.VelocityProxy;
|
||||||
import net.minestom.server.network.ConnectionManager;
|
import net.minestom.server.network.ConnectionManager;
|
||||||
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
import net.minestom.server.network.packet.client.ClientPreplayPacket;
|
||||||
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
|
||||||
import net.minestom.server.network.player.GameProfile;
|
import net.minestom.server.network.player.GameProfile;
|
||||||
@ -18,6 +19,10 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.SocketAddress;
|
import java.net.SocketAddress;
|
||||||
|
import java.net.UnknownHostException;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
import static net.minestom.server.network.NetworkBuffer.STRING;
|
||||||
|
|
||||||
public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) implements ClientPreplayPacket {
|
public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) implements ClientPreplayPacket {
|
||||||
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
private final static ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
|
||||||
@ -41,14 +46,20 @@ public record LoginPluginResponsePacket(int messageId, byte @Nullable [] data) i
|
|||||||
// Velocity
|
// Velocity
|
||||||
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
|
if (VelocityProxy.isEnabled() && channel.equals(VelocityProxy.PLAYER_INFO_CHANNEL)) {
|
||||||
if (data != null && data.length > 0) {
|
if (data != null && data.length > 0) {
|
||||||
BinaryReader reader = new BinaryReader(data);
|
NetworkBuffer buffer = new NetworkBuffer(ByteBuffer.wrap(data));
|
||||||
success = VelocityProxy.checkIntegrity(reader);
|
success = VelocityProxy.checkIntegrity(buffer);
|
||||||
if (success) {
|
if (success) {
|
||||||
// Get the real connection address
|
// Get the real connection address
|
||||||
final InetAddress address = VelocityProxy.readAddress(reader);
|
final InetAddress address;
|
||||||
|
try {
|
||||||
|
address = InetAddress.getByName(buffer.read(STRING));
|
||||||
|
} catch (UnknownHostException e) {
|
||||||
|
MinecraftServer.getExceptionManager().handleException(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
|
final int port = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getPort();
|
||||||
socketAddress = new InetSocketAddress(address, port);
|
socketAddress = new InetSocketAddress(address, port);
|
||||||
gameProfile = new GameProfile(reader);
|
gameProfile = new GameProfile(new BinaryReader(buffer));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,9 +3,9 @@ package net.minestom.server.tag;
|
|||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
import net.minestom.server.utils.Utils;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.*;
|
import org.jglrxavpok.hephaistos.nbt.*;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -22,8 +22,8 @@ final class Serializers {
|
|||||||
static final Entry<String, NBTString> STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String);
|
static final Entry<String, NBTString> STRING = new Entry<>(NBTType.TAG_String, NBTString::getValue, NBT::String);
|
||||||
static final Entry<NBT, NBT> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
|
static final Entry<NBT, NBT> NBT_ENTRY = new Entry<>(null, Function.identity(), Function.identity());
|
||||||
|
|
||||||
static final Entry<java.util.UUID, NBTIntArray> UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> Utils.intArrayToUuid(intArray.getValue().copyArray()),
|
static final Entry<java.util.UUID, NBTIntArray> UUID = new Entry<>(NBTType.TAG_Int_Array, intArray -> intArrayToUuid(intArray.getValue().copyArray()),
|
||||||
uuid -> NBT.IntArray(Utils.uuidToIntArray(uuid)));
|
uuid -> NBT.IntArray(uuidToIntArray(uuid)));
|
||||||
static final Entry<ItemStack, NBTCompound> ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT);
|
static final Entry<ItemStack, NBTCompound> ITEM = new Entry<>(NBTType.TAG_Compound, ItemStack::fromItemNBT, ItemStack::toItemNBT);
|
||||||
static final Entry<Component, NBTString> COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()),
|
static final Entry<Component, NBTString> COMPONENT = new Entry<>(NBTType.TAG_String, input -> GsonComponentSerializer.gson().deserialize(input.getValue()),
|
||||||
component -> NBT.String(GsonComponentSerializer.gson().serialize(component)));
|
component -> NBT.String(GsonComponentSerializer.gson().serialize(component)));
|
||||||
@ -55,4 +55,26 @@ final class Serializers {
|
|||||||
return writer.apply(value);
|
return writer.apply(value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int[] uuidToIntArray(UUID uuid) {
|
||||||
|
int[] array = new int[4];
|
||||||
|
|
||||||
|
final long uuidMost = uuid.getMostSignificantBits();
|
||||||
|
final long uuidLeast = uuid.getLeastSignificantBits();
|
||||||
|
|
||||||
|
array[0] = (int) (uuidMost >> 32);
|
||||||
|
array[1] = (int) uuidMost;
|
||||||
|
|
||||||
|
array[2] = (int) (uuidLeast >> 32);
|
||||||
|
array[3] = (int) uuidLeast;
|
||||||
|
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static UUID intArrayToUuid(int[] array) {
|
||||||
|
final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
|
||||||
|
final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
|
||||||
|
|
||||||
|
return new UUID(uuidMost, uuidLeast);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,116 +0,0 @@
|
|||||||
package net.minestom.server.utils;
|
|
||||||
|
|
||||||
import net.kyori.adventure.nbt.api.BinaryTagHolder;
|
|
||||||
import net.kyori.adventure.util.Codec;
|
|
||||||
import net.minestom.server.adventure.MinestomAdventure;
|
|
||||||
import net.minestom.server.inventory.Inventory;
|
|
||||||
import net.minestom.server.item.Enchantment;
|
|
||||||
import net.minestom.server.item.ItemStack;
|
|
||||||
import net.minestom.server.item.Material;
|
|
||||||
import net.minestom.server.utils.binary.BinaryReader;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
|
||||||
import org.jetbrains.annotations.Contract;
|
|
||||||
import org.jetbrains.annotations.NotNull;
|
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.*;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.mutable.MutableNBTCompound;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
// for lack of a better name
|
|
||||||
@ApiStatus.Internal
|
|
||||||
public final class NBTUtils {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An Adventure codec to convert between NBT and SNBT.
|
|
||||||
*
|
|
||||||
* @deprecated Use {@link MinestomAdventure#NBT_CODEC}
|
|
||||||
*/
|
|
||||||
@Deprecated(forRemoval = true)
|
|
||||||
public static final Codec<NBT, String, NBTException, RuntimeException> SNBT_CODEC = MinestomAdventure.NBT_CODEC;
|
|
||||||
|
|
||||||
private NBTUtils() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Turns an {@link NBTCompound} into an Adventure {@link BinaryTagHolder}.
|
|
||||||
*
|
|
||||||
* @param tag the tag, if any
|
|
||||||
* @return the binary tag holder, or {@code null} if the tag was null
|
|
||||||
*/
|
|
||||||
@Contract("null -> null; !null -> !null")
|
|
||||||
public static BinaryTagHolder asBinaryTagHolder(@Nullable NBTCompound tag) {
|
|
||||||
if (tag == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
return BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Loads all the items from the 'items' list into the given inventory
|
|
||||||
*
|
|
||||||
* @param items the items to save
|
|
||||||
* @param destination the inventory destination
|
|
||||||
*/
|
|
||||||
public static void loadAllItems(@NotNull NBTList<NBTCompound> items, @NotNull Inventory destination) {
|
|
||||||
destination.clear();
|
|
||||||
for (NBTCompound tag : items) {
|
|
||||||
Material material = Material.fromNamespaceId(tag.getString("id"));
|
|
||||||
if (material == Material.AIR) {
|
|
||||||
material = Material.STONE;
|
|
||||||
}
|
|
||||||
byte count = tag.getByte("Count");
|
|
||||||
NBTCompound nbtCompound = null;
|
|
||||||
if (tag.containsKey("tag")) {
|
|
||||||
nbtCompound = tag.getCompound("tag");
|
|
||||||
}
|
|
||||||
ItemStack itemStack = ItemStack.fromNBT(material, nbtCompound, count);
|
|
||||||
destination.setItemStack(tag.getByte("Slot"), itemStack);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void saveAllItems(@NotNull List<NBTCompound> list, @NotNull Inventory inventory) {
|
|
||||||
for (int i = 0; i < inventory.getSize(); i++) {
|
|
||||||
final ItemStack stack = inventory.getItemStack(i);
|
|
||||||
final NBTCompound tag = stack.meta().toNBT();
|
|
||||||
final int slotIndex = i;
|
|
||||||
list.add(NBT.Compound(nbt -> {
|
|
||||||
nbt.set("tag", tag);
|
|
||||||
nbt.setByte("Slot", (byte) slotIndex);
|
|
||||||
nbt.setByte("Count", (byte) stack.amount());
|
|
||||||
nbt.setString("id", stack.material().name());
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeEnchant(@NotNull MutableNBTCompound nbt, @NotNull String listName,
|
|
||||||
@NotNull Map<Enchantment, Short> enchantmentMap) {
|
|
||||||
nbt.set(listName, NBT.List(
|
|
||||||
NBTType.TAG_Compound,
|
|
||||||
enchantmentMap.entrySet().stream()
|
|
||||||
.map(entry -> NBT.Compound(Map.of(
|
|
||||||
"lvl", NBT.Short(entry.getValue()),
|
|
||||||
"id", NBT.String(entry.getKey().name()))))
|
|
||||||
.toList()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
public static @NotNull ItemStack readItemStack(@NotNull BinaryReader reader) {
|
|
||||||
final boolean present = reader.readBoolean();
|
|
||||||
if (!present) return ItemStack.AIR;
|
|
||||||
|
|
||||||
final int id = reader.readVarInt();
|
|
||||||
if (id == -1) {
|
|
||||||
// Drop mode
|
|
||||||
return ItemStack.AIR;
|
|
||||||
}
|
|
||||||
|
|
||||||
final Material material = Material.fromId((short) id);
|
|
||||||
final byte count = reader.readByte();
|
|
||||||
NBTCompound nbtCompound = reader.readTag() instanceof NBTCompound compound ?
|
|
||||||
compound : null;
|
|
||||||
return ItemStack.fromNBT(material, nbtCompound, count);
|
|
||||||
}
|
|
||||||
}
|
|
@ -17,6 +17,7 @@ import net.minestom.server.adventure.MinestomAdventure;
|
|||||||
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
import net.minestom.server.adventure.audience.PacketGroupingAudience;
|
||||||
import net.minestom.server.entity.Entity;
|
import net.minestom.server.entity.Entity;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.network.packet.server.*;
|
import net.minestom.server.network.packet.server.*;
|
||||||
import net.minestom.server.network.player.PlayerConnection;
|
import net.minestom.server.network.player.PlayerConnection;
|
||||||
import net.minestom.server.network.player.PlayerSocketConnection;
|
import net.minestom.server.network.player.PlayerSocketConnection;
|
||||||
@ -255,24 +256,26 @@ public final class PacketUtils {
|
|||||||
int id,
|
int id,
|
||||||
@NotNull Writeable writeable,
|
@NotNull Writeable writeable,
|
||||||
int compressionThreshold) {
|
int compressionThreshold) {
|
||||||
BinaryWriter writerView = BinaryWriter.view(buffer); // ensure that the buffer is not resized
|
NetworkBuffer networkBuffer = new NetworkBuffer(buffer, false);
|
||||||
|
BinaryWriter legacyWriter = new BinaryWriter(networkBuffer);
|
||||||
if (compressionThreshold <= 0) {
|
if (compressionThreshold <= 0) {
|
||||||
// Uncompressed format https://wiki.vg/Protocol#Without_compression
|
// Uncompressed format https://wiki.vg/Protocol#Without_compression
|
||||||
final int lengthIndex = Utils.writeEmptyVarIntHeader(buffer);
|
final int lengthIndex = networkBuffer.skipWrite(3);
|
||||||
Utils.writeVarInt(buffer, id);
|
networkBuffer.write(NetworkBuffer.VAR_INT, id);
|
||||||
writeable.write(writerView);
|
writeable.write(legacyWriter);
|
||||||
final int finalSize = buffer.position() - (lengthIndex + 3);
|
final int finalSize = networkBuffer.writeIndex() - (lengthIndex + 3);
|
||||||
Utils.writeVarIntHeader(buffer, lengthIndex, finalSize);
|
Utils.writeVarIntHeader(buffer, lengthIndex, finalSize);
|
||||||
|
buffer.position(networkBuffer.writeIndex());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// Compressed format https://wiki.vg/Protocol#With_compression
|
// Compressed format https://wiki.vg/Protocol#With_compression
|
||||||
final int compressedIndex = Utils.writeEmptyVarIntHeader(buffer);
|
final int compressedIndex = networkBuffer.skipWrite(3);
|
||||||
final int uncompressedIndex = Utils.writeEmptyVarIntHeader(buffer);
|
final int uncompressedIndex = networkBuffer.skipWrite(3);
|
||||||
|
|
||||||
final int contentStart = buffer.position();
|
final int contentStart = networkBuffer.writeIndex();
|
||||||
Utils.writeVarInt(buffer, id);
|
networkBuffer.write(NetworkBuffer.VAR_INT, id);
|
||||||
writeable.write(writerView);
|
writeable.write(legacyWriter);
|
||||||
final int packetSize = buffer.position() - contentStart;
|
final int packetSize = networkBuffer.writeIndex() - contentStart;
|
||||||
final boolean compressed = packetSize >= compressionThreshold;
|
final boolean compressed = packetSize >= compressionThreshold;
|
||||||
if (compressed) {
|
if (compressed) {
|
||||||
// Packet large enough, compress it
|
// Packet large enough, compress it
|
||||||
@ -283,11 +286,15 @@ public final class PacketUtils {
|
|||||||
deflater.finish();
|
deflater.finish();
|
||||||
deflater.deflate(buffer.position(contentStart));
|
deflater.deflate(buffer.position(contentStart));
|
||||||
deflater.reset();
|
deflater.reset();
|
||||||
|
|
||||||
|
networkBuffer.skipWrite(buffer.position() - contentStart);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Packet header (Packet + Data Length)
|
// Packet header (Packet + Data Length)
|
||||||
Utils.writeVarIntHeader(buffer, compressedIndex, buffer.position() - uncompressedIndex);
|
Utils.writeVarIntHeader(buffer, compressedIndex, networkBuffer.writeIndex() - uncompressedIndex);
|
||||||
Utils.writeVarIntHeader(buffer, uncompressedIndex, compressed ? packetSize : 0);
|
Utils.writeVarIntHeader(buffer, uncompressedIndex, compressed ? packetSize : 0);
|
||||||
|
|
||||||
|
buffer.position(networkBuffer.writeIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiStatus.Internal
|
@ApiStatus.Internal
|
||||||
|
@ -1,22 +0,0 @@
|
|||||||
package net.minestom.server.utils;
|
|
||||||
|
|
||||||
import net.minestom.server.coordinate.Point;
|
|
||||||
import net.minestom.server.coordinate.Vec;
|
|
||||||
|
|
||||||
public final class SerializerUtils {
|
|
||||||
|
|
||||||
private SerializerUtils() {
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public static long positionToLong(int x, int y, int z) {
|
|
||||||
return (((long) x & 0x3FFFFFF) << 38) | (((long) z & 0x3FFFFFF) << 12) | ((long) y & 0xFFF);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static Point longToBlockPosition(long value) {
|
|
||||||
final int x = (int) (value >> 38);
|
|
||||||
final int y = (int) (value << 52 >> 52);
|
|
||||||
final int z = (int) (value << 26 >> 38);
|
|
||||||
return new Vec(x, y, z);
|
|
||||||
}
|
|
||||||
}
|
|
@ -45,12 +45,6 @@ public final class Utils {
|
|||||||
buffer.put(startIndex + 2, (byte) (value >>> 14));
|
buffer.put(startIndex + 2, (byte) (value >>> 14));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static int writeEmptyVarIntHeader(@NotNull ByteBuffer buffer) {
|
|
||||||
final int index = buffer.position();
|
|
||||||
buffer.position(index + 3); // Skip 3 bytes
|
|
||||||
return index;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int readVarInt(ByteBuffer buf) {
|
public static int readVarInt(ByteBuffer buf) {
|
||||||
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
|
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L393
|
||||||
int result = 0;
|
int result = 0;
|
||||||
@ -62,94 +56,4 @@ public final class Utils {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static long readVarLong(@NotNull ByteBuffer buf) {
|
|
||||||
// https://github.com/jvm-profiling-tools/async-profiler/blob/a38a375dc62b31a8109f3af97366a307abb0fe6f/src/converter/one/jfr/JfrReader.java#L404
|
|
||||||
long result = 0;
|
|
||||||
for (int shift = 0; shift < 56; shift += 7) {
|
|
||||||
byte b = buf.get();
|
|
||||||
result |= (b & 0x7fL) << shift;
|
|
||||||
if (b >= 0) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result | (buf.get() & 0xffL) << 56;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void writeVarLong(ByteBuffer buffer, long value) {
|
|
||||||
do {
|
|
||||||
byte temp = (byte) (value & 0b01111111);
|
|
||||||
value >>>= 7;
|
|
||||||
if (value != 0) {
|
|
||||||
temp |= 0b10000000;
|
|
||||||
}
|
|
||||||
buffer.put(temp);
|
|
||||||
} while (value != 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static int[] uuidToIntArray(UUID uuid) {
|
|
||||||
int[] array = new int[4];
|
|
||||||
|
|
||||||
final long uuidMost = uuid.getMostSignificantBits();
|
|
||||||
final long uuidLeast = uuid.getLeastSignificantBits();
|
|
||||||
|
|
||||||
array[0] = (int) (uuidMost >> 32);
|
|
||||||
array[1] = (int) uuidMost;
|
|
||||||
|
|
||||||
array[2] = (int) (uuidLeast >> 32);
|
|
||||||
array[3] = (int) uuidLeast;
|
|
||||||
|
|
||||||
return array;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static UUID intArrayToUuid(int[] array) {
|
|
||||||
final long uuidMost = (long) array[0] << 32 | array[1] & 0xFFFFFFFFL;
|
|
||||||
final long uuidLeast = (long) array[2] << 32 | array[3] & 0xFFFFFFFFL;
|
|
||||||
|
|
||||||
return new UUID(uuidMost, uuidLeast);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static final int[] MAGIC = {
|
|
||||||
-1, -1, 0, Integer.MIN_VALUE, 0, 0, 1431655765, 1431655765, 0, Integer.MIN_VALUE,
|
|
||||||
0, 1, 858993459, 858993459, 0, 715827882, 715827882, 0, 613566756, 613566756,
|
|
||||||
0, Integer.MIN_VALUE, 0, 2, 477218588, 477218588, 0, 429496729, 429496729, 0,
|
|
||||||
390451572, 390451572, 0, 357913941, 357913941, 0, 330382099, 330382099, 0, 306783378,
|
|
||||||
306783378, 0, 286331153, 286331153, 0, Integer.MIN_VALUE, 0, 3, 252645135, 252645135,
|
|
||||||
0, 238609294, 238609294, 0, 226050910, 226050910, 0, 214748364, 214748364, 0,
|
|
||||||
204522252, 204522252, 0, 195225786, 195225786, 0, 186737708, 186737708, 0, 178956970,
|
|
||||||
178956970, 0, 171798691, 171798691, 0, 165191049, 165191049, 0, 159072862, 159072862,
|
|
||||||
0, 153391689, 153391689, 0, 148102320, 148102320, 0, 143165576, 143165576, 0,
|
|
||||||
138547332, 138547332, 0, Integer.MIN_VALUE, 0, 4, 130150524, 130150524, 0, 126322567,
|
|
||||||
126322567, 0, 122713351, 122713351, 0, 119304647, 119304647, 0, 116080197, 116080197,
|
|
||||||
0, 113025455, 113025455, 0, 110127366, 110127366, 0, 107374182, 107374182, 0,
|
|
||||||
104755299, 104755299, 0, 102261126, 102261126, 0, 99882960, 99882960, 0, 97612893,
|
|
||||||
97612893, 0, 95443717, 95443717, 0, 93368854, 93368854, 0, 91382282, 91382282,
|
|
||||||
0, 89478485, 89478485, 0, 87652393, 87652393, 0, 85899345, 85899345, 0,
|
|
||||||
84215045, 84215045, 0, 82595524, 82595524, 0, 81037118, 81037118, 0, 79536431,
|
|
||||||
79536431, 0, 78090314, 78090314, 0, 76695844, 76695844, 0, 75350303, 75350303,
|
|
||||||
0, 74051160, 74051160, 0, 72796055, 72796055, 0, 71582788, 71582788, 0,
|
|
||||||
70409299, 70409299, 0, 69273666, 69273666, 0, 68174084, 68174084, 0, Integer.MIN_VALUE,
|
|
||||||
0, 5};
|
|
||||||
|
|
||||||
public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) {
|
|
||||||
final long maxEntryValue = (1L << bitsPerEntry) - 1;
|
|
||||||
final char valuesPerLong = (char) (64 / bitsPerEntry);
|
|
||||||
final int magicIndex = 3 * (valuesPerLong - 1);
|
|
||||||
final long divideMul = Integer.toUnsignedLong(MAGIC[magicIndex]);
|
|
||||||
final long divideAdd = Integer.toUnsignedLong(MAGIC[magicIndex + 1]);
|
|
||||||
final int divideShift = MAGIC[magicIndex + 2];
|
|
||||||
final int size = (blocks.length + valuesPerLong - 1) / valuesPerLong;
|
|
||||||
|
|
||||||
long[] data = new long[size];
|
|
||||||
|
|
||||||
for (int i = 0; i < blocks.length; i++) {
|
|
||||||
final long value = blocks[i];
|
|
||||||
final int cellIndex = (int) (i * divideMul + divideAdd >> 32L >> divideShift);
|
|
||||||
final int bitIndex = (i - cellIndex * valuesPerLong) * bitsPerEntry;
|
|
||||||
data[cellIndex] = data[cellIndex] & ~(maxEntryValue << bitIndex) | (value & maxEntryValue) << bitIndex;
|
|
||||||
}
|
|
||||||
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -2,21 +2,14 @@ package net.minestom.server.utils.binary;
|
|||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.utils.Either;
|
import net.minestom.server.utils.Either;
|
||||||
import net.minestom.server.utils.NBTUtils;
|
|
||||||
import net.minestom.server.utils.SerializerUtils;
|
|
||||||
import net.minestom.server.utils.Utils;
|
|
||||||
import net.minestom.server.utils.validate.Check;
|
import net.minestom.server.utils.validate.Check;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBTException;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBTReader;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.BufferUnderflowException;
|
import java.nio.BufferUnderflowException;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -27,17 +20,22 @@ import java.util.UUID;
|
|||||||
import java.util.function.Function;
|
import java.util.function.Function;
|
||||||
import java.util.function.Supplier;
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
import static net.minestom.server.network.NetworkBuffer.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to read from a byte array.
|
* Class used to read from a byte array.
|
||||||
* <p>
|
* <p>
|
||||||
* WARNING: not thread-safe.
|
* WARNING: not thread-safe.
|
||||||
*/
|
*/
|
||||||
public class BinaryReader extends InputStream {
|
public class BinaryReader extends InputStream {
|
||||||
private final ByteBuffer buffer;
|
private final NetworkBuffer buffer;
|
||||||
private NBTReader nbtReader = null;
|
|
||||||
|
public BinaryReader(NetworkBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
public BinaryReader(@NotNull ByteBuffer buffer) {
|
public BinaryReader(@NotNull ByteBuffer buffer) {
|
||||||
this.buffer = buffer;
|
this.buffer = new NetworkBuffer(buffer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryReader(byte[] bytes) {
|
public BinaryReader(byte[] bytes) {
|
||||||
@ -45,57 +43,53 @@ public class BinaryReader extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int readVarInt() {
|
public int readVarInt() {
|
||||||
return Utils.readVarInt(buffer);
|
return buffer.read(VAR_INT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readVarLong() {
|
public long readVarLong() {
|
||||||
return Utils.readVarLong(buffer);
|
return buffer.read(VAR_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean readBoolean() {
|
public boolean readBoolean() {
|
||||||
return buffer.get() == 1;
|
return buffer.read(BOOLEAN);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte readByte() {
|
public byte readByte() {
|
||||||
return buffer.get();
|
return buffer.read(BYTE);
|
||||||
}
|
}
|
||||||
|
|
||||||
public short readShort() {
|
public short readShort() {
|
||||||
return buffer.getShort();
|
return buffer.read(SHORT);
|
||||||
}
|
|
||||||
|
|
||||||
public char readChar() {
|
|
||||||
return buffer.getChar();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int readUnsignedShort() {
|
public int readUnsignedShort() {
|
||||||
return buffer.getShort() & 0xFFFF;
|
return buffer.read(SHORT) & 0xFFFF;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as readInt
|
* Same as readInt
|
||||||
*/
|
*/
|
||||||
public int readInteger() {
|
public int readInteger() {
|
||||||
return buffer.getInt();
|
return buffer.read(INT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Same as readInteger, created for parity with BinaryWriter
|
* Same as readInteger, created for parity with BinaryWriter
|
||||||
*/
|
*/
|
||||||
public int readInt() {
|
public int readInt() {
|
||||||
return buffer.getInt();
|
return buffer.read(INT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public long readLong() {
|
public long readLong() {
|
||||||
return buffer.getLong();
|
return buffer.read(LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public float readFloat() {
|
public float readFloat() {
|
||||||
return buffer.getFloat();
|
return buffer.read(FLOAT);
|
||||||
}
|
}
|
||||||
|
|
||||||
public double readDouble() {
|
public double readDouble() {
|
||||||
return buffer.getDouble();
|
return buffer.read(DOUBLE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -112,9 +106,11 @@ public class BinaryReader extends InputStream {
|
|||||||
final int length = readVarInt();
|
final int length = readVarInt();
|
||||||
byte[] bytes = new byte[length];
|
byte[] bytes = new byte[length];
|
||||||
try {
|
try {
|
||||||
this.buffer.get(bytes);
|
for (int i = 0; i < length; i++) {
|
||||||
|
bytes[i] = readByte();
|
||||||
|
}
|
||||||
} catch (BufferUnderflowException e) {
|
} catch (BufferUnderflowException e) {
|
||||||
throw new RuntimeException("Could not read " + length + ", " + buffer.remaining() + " remaining.");
|
throw new RuntimeException("Could not read " + length + ", " + buffer.readableBytes() + " remaining.");
|
||||||
}
|
}
|
||||||
final String str = new String(bytes, StandardCharsets.UTF_8);
|
final String str = new String(bytes, StandardCharsets.UTF_8);
|
||||||
Check.stateCondition(str.length() > maxLength,
|
Check.stateCondition(str.length() > maxLength,
|
||||||
@ -123,12 +119,14 @@ public class BinaryReader extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public String readSizedString() {
|
public String readSizedString() {
|
||||||
return readSizedString(Integer.MAX_VALUE);
|
return buffer.read(STRING);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readBytes(int length) {
|
public byte[] readBytes(int length) {
|
||||||
byte[] bytes = new byte[length];
|
byte[] bytes = new byte[length];
|
||||||
buffer.get(bytes);
|
for (int i = 0; i < length; i++) {
|
||||||
|
bytes[i] = readByte();
|
||||||
|
}
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -177,27 +175,19 @@ public class BinaryReader extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public byte[] readRemainingBytes() {
|
public byte[] readRemainingBytes() {
|
||||||
return readBytes(available());
|
return buffer.read(RAW_BYTES);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Point readBlockPosition() {
|
public Point readBlockPosition() {
|
||||||
return SerializerUtils.longToBlockPosition(buffer.getLong());
|
return buffer.read(BLOCK_POSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public UUID readUuid() {
|
public UUID readUuid() {
|
||||||
return new UUID(readLong(), readLong());
|
return buffer.read(UUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Tries to read an {@link ItemStack}.
|
|
||||||
*
|
|
||||||
* @return the read item
|
|
||||||
* @throws NullPointerException if the item could not get read
|
|
||||||
*/
|
|
||||||
public ItemStack readItemStack() {
|
public ItemStack readItemStack() {
|
||||||
final ItemStack itemStack = NBTUtils.readItemStack(this);
|
return buffer.read(ITEM);
|
||||||
Check.notNull(itemStack, "#readSlot returned null, probably because the buffer was corrupted");
|
|
||||||
return itemStack;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Component readComponent(int maxLength) {
|
public Component readComponent(int maxLength) {
|
||||||
@ -206,7 +196,7 @@ public class BinaryReader extends InputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Component readComponent() {
|
public Component readComponent() {
|
||||||
return readComponent(Integer.MAX_VALUE);
|
return buffer.read(COMPONENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -263,10 +253,6 @@ public class BinaryReader extends InputStream {
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ByteBuffer getBuffer() {
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int read() {
|
public int read() {
|
||||||
return readByte() & 0xFF;
|
return readByte() & 0xFF;
|
||||||
@ -274,21 +260,11 @@ public class BinaryReader extends InputStream {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int available() {
|
public int available() {
|
||||||
return buffer.remaining();
|
return buffer.readableBytes();
|
||||||
}
|
}
|
||||||
|
|
||||||
public NBT readTag() {
|
public NBT readTag() {
|
||||||
NBTReader reader = this.nbtReader;
|
return buffer.read(NBT);
|
||||||
if (reader == null) {
|
|
||||||
reader = new NBTReader(this, CompressedProcesser.NONE);
|
|
||||||
this.nbtReader = reader;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return reader.read();
|
|
||||||
} catch (IOException | NBTException e) {
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
throw new RuntimeException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -299,11 +275,12 @@ public class BinaryReader extends InputStream {
|
|||||||
* @param extractor the extraction code, simply call the reader's read* methods here.
|
* @param extractor the extraction code, simply call the reader's read* methods here.
|
||||||
*/
|
*/
|
||||||
public byte[] extractBytes(Runnable extractor) {
|
public byte[] extractBytes(Runnable extractor) {
|
||||||
int startingPosition = buffer.position();
|
int startingPosition = buffer.readIndex();
|
||||||
extractor.run();
|
extractor.run();
|
||||||
int endingPosition = getBuffer().position();
|
int endingPosition = buffer.readIndex();
|
||||||
byte[] output = new byte[endingPosition - startingPosition];
|
byte[] output = new byte[endingPosition - startingPosition];
|
||||||
buffer.get(startingPosition, output);
|
buffer.copyTo(buffer.readIndex(), output, 0, output.length);
|
||||||
|
//buffer.get(startingPosition, output);
|
||||||
return output;
|
return output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,49 +1,42 @@
|
|||||||
package net.minestom.server.utils.binary;
|
package net.minestom.server.utils.binary;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
|
||||||
import net.minestom.server.MinecraftServer;
|
|
||||||
import net.minestom.server.coordinate.Point;
|
import net.minestom.server.coordinate.Point;
|
||||||
|
import net.minestom.server.coordinate.Vec;
|
||||||
import net.minestom.server.item.ItemStack;
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.network.NetworkBuffer;
|
||||||
import net.minestom.server.utils.Either;
|
import net.minestom.server.utils.Either;
|
||||||
import net.minestom.server.utils.SerializerUtils;
|
|
||||||
import net.minestom.server.utils.Utils;
|
|
||||||
import org.jetbrains.annotations.ApiStatus;
|
import org.jetbrains.annotations.ApiStatus;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jglrxavpok.hephaistos.nbt.CompressedProcesser;
|
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBT;
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||||
import org.jglrxavpok.hephaistos.nbt.NBTWriter;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.function.BiConsumer;
|
import java.util.function.BiConsumer;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
import static net.minestom.server.network.NetworkBuffer.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class used to write to a byte array.
|
* Class used to write to a byte array.
|
||||||
* WARNING: not thread-safe.
|
* WARNING: not thread-safe.
|
||||||
*/
|
*/
|
||||||
public class BinaryWriter extends OutputStream {
|
public class BinaryWriter extends OutputStream {
|
||||||
private ByteBuffer buffer;
|
private final NetworkBuffer buffer;
|
||||||
private NBTWriter nbtWriter; // Lazily initialized
|
|
||||||
|
|
||||||
private final boolean resizable;
|
public BinaryWriter(NetworkBuffer buffer) {
|
||||||
|
this.buffer = buffer;
|
||||||
|
}
|
||||||
|
|
||||||
private BinaryWriter(ByteBuffer buffer, boolean resizable) {
|
private BinaryWriter(ByteBuffer buffer, boolean resizable) {
|
||||||
this.buffer = buffer;
|
this.buffer = new NetworkBuffer(buffer, resizable);
|
||||||
this.resizable = resizable;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryWriter(@NotNull ByteBuffer buffer) {
|
public BinaryWriter(@NotNull ByteBuffer buffer) {
|
||||||
this.buffer = buffer;
|
this.buffer = new NetworkBuffer(buffer);
|
||||||
this.resizable = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public BinaryWriter(int initialCapacity) {
|
public BinaryWriter(int initialCapacity) {
|
||||||
@ -59,158 +52,55 @@ public class BinaryWriter extends OutputStream {
|
|||||||
return new BinaryWriter(buffer, false);
|
return new BinaryWriter(buffer, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void ensureSize(int length) {
|
|
||||||
if (!resizable) return;
|
|
||||||
final int position = buffer.position();
|
|
||||||
if (position + length >= buffer.limit()) {
|
|
||||||
final int newLength = (position + length) * 4;
|
|
||||||
var copy = buffer.isDirect() ?
|
|
||||||
ByteBuffer.allocateDirect(newLength) : ByteBuffer.allocate(newLength);
|
|
||||||
copy.put(buffer.flip());
|
|
||||||
this.buffer = copy;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a component to the buffer as a sized string.
|
|
||||||
*
|
|
||||||
* @param component the component
|
|
||||||
*/
|
|
||||||
public void writeComponent(@NotNull Component component) {
|
public void writeComponent(@NotNull Component component) {
|
||||||
this.writeSizedString(GsonComponentSerializer.gson().serialize(component));
|
this.buffer.write(COMPONENT, component);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single byte to the buffer.
|
|
||||||
*
|
|
||||||
* @param b the byte to write
|
|
||||||
*/
|
|
||||||
public void writeByte(byte b) {
|
public void writeByte(byte b) {
|
||||||
ensureSize(Byte.BYTES);
|
this.buffer.write(BYTE, b);
|
||||||
buffer.put(b);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single boolean to the buffer.
|
|
||||||
*
|
|
||||||
* @param b the boolean to write
|
|
||||||
*/
|
|
||||||
public void writeBoolean(boolean b) {
|
public void writeBoolean(boolean b) {
|
||||||
writeByte((byte) (b ? 1 : 0));
|
this.buffer.write(BOOLEAN, b);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single char to the buffer.
|
|
||||||
*
|
|
||||||
* @param c the char to write
|
|
||||||
*/
|
|
||||||
public void writeChar(char c) {
|
|
||||||
ensureSize(Character.BYTES);
|
|
||||||
buffer.putChar(c);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single short to the buffer.
|
|
||||||
*
|
|
||||||
* @param s the short to write
|
|
||||||
*/
|
|
||||||
public void writeShort(short s) {
|
public void writeShort(short s) {
|
||||||
ensureSize(Short.BYTES);
|
this.buffer.write(SHORT, s);
|
||||||
buffer.putShort(s);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single int to the buffer.
|
|
||||||
*
|
|
||||||
* @param i the int to write
|
|
||||||
*/
|
|
||||||
public void writeInt(int i) {
|
public void writeInt(int i) {
|
||||||
ensureSize(Integer.BYTES);
|
this.buffer.write(INT, i);
|
||||||
buffer.putInt(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single long to the buffer.
|
|
||||||
*
|
|
||||||
* @param l the long to write
|
|
||||||
*/
|
|
||||||
public void writeLong(long l) {
|
public void writeLong(long l) {
|
||||||
ensureSize(Long.BYTES);
|
this.buffer.write(LONG, l);
|
||||||
buffer.putLong(l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single float to the buffer.
|
|
||||||
*
|
|
||||||
* @param f the float to write
|
|
||||||
*/
|
|
||||||
public void writeFloat(float f) {
|
public void writeFloat(float f) {
|
||||||
ensureSize(Float.BYTES);
|
this.buffer.write(FLOAT, f);
|
||||||
buffer.putFloat(f);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single double to the buffer.
|
|
||||||
*
|
|
||||||
* @param d the double to write
|
|
||||||
*/
|
|
||||||
public void writeDouble(double d) {
|
public void writeDouble(double d) {
|
||||||
ensureSize(Double.BYTES);
|
this.buffer.write(DOUBLE, d);
|
||||||
buffer.putDouble(d);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single var-int to the buffer.
|
|
||||||
*
|
|
||||||
* @param i the int to write
|
|
||||||
*/
|
|
||||||
public void writeVarInt(int i) {
|
public void writeVarInt(int i) {
|
||||||
ensureSize(5);
|
this.buffer.write(VAR_INT, i);
|
||||||
Utils.writeVarInt(buffer, i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a single var-long to the buffer.
|
|
||||||
*
|
|
||||||
* @param l the long to write
|
|
||||||
*/
|
|
||||||
public void writeVarLong(long l) {
|
public void writeVarLong(long l) {
|
||||||
ensureSize(10);
|
this.buffer.write(VAR_LONG, l);
|
||||||
Utils.writeVarLong(buffer, l);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a string to the buffer.
|
|
||||||
* <p>
|
|
||||||
* The size is a var-int type.
|
|
||||||
*
|
|
||||||
* @param string the string to write
|
|
||||||
*/
|
|
||||||
public void writeSizedString(@NotNull String string) {
|
public void writeSizedString(@NotNull String string) {
|
||||||
final var bytes = string.getBytes(StandardCharsets.UTF_8);
|
this.buffer.write(STRING, string);
|
||||||
writeVarInt(bytes.length);
|
|
||||||
writeBytes(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a null terminated string to the buffer. This method adds the null character
|
|
||||||
* to the end of the string before writing.
|
|
||||||
*
|
|
||||||
* @param string the string to write
|
|
||||||
* @param charset the charset to encode in
|
|
||||||
*/
|
|
||||||
public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
|
public void writeNullTerminatedString(@NotNull String string, @NotNull Charset charset) {
|
||||||
final var bytes = (string + '\0').getBytes(charset);
|
final var bytes = (string + '\0').getBytes(charset);
|
||||||
writeBytes(bytes);
|
writeBytes(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a var-int array to the buffer.
|
|
||||||
* <p>
|
|
||||||
* It is sized by another var-int at the beginning.
|
|
||||||
*
|
|
||||||
* @param array the integers to write
|
|
||||||
*/
|
|
||||||
public void writeVarIntArray(int[] array) {
|
public void writeVarIntArray(int[] array) {
|
||||||
if (array == null) {
|
if (array == null) {
|
||||||
writeVarInt(0);
|
writeVarInt(0);
|
||||||
@ -253,46 +143,16 @@ public class BinaryWriter extends OutputStream {
|
|||||||
writeBytes(array);
|
writeBytes(array);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a byte array.
|
|
||||||
* <p>
|
|
||||||
* WARNING: it doesn't write the length of {@code bytes}.
|
|
||||||
*
|
|
||||||
* @param bytes the byte array to write
|
|
||||||
*/
|
|
||||||
public void writeBytes(byte @NotNull [] bytes) {
|
public void writeBytes(byte @NotNull [] bytes) {
|
||||||
if (bytes.length == 0) return;
|
this.buffer.write(RAW_BYTES, bytes);
|
||||||
ensureSize(bytes.length);
|
|
||||||
buffer.put(bytes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes a string to the buffer.
|
|
||||||
* <p>
|
|
||||||
* The array is sized by a var-int and all strings are wrote using {@link #writeSizedString(String)}.
|
|
||||||
*
|
|
||||||
* @param array the string array to write
|
|
||||||
*/
|
|
||||||
public void writeStringArray(@NotNull String[] array) {
|
public void writeStringArray(@NotNull String[] array) {
|
||||||
if (array == null) {
|
this.buffer.writeCollection(STRING, array);
|
||||||
writeVarInt(0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
writeVarInt(array.length);
|
|
||||||
for (String element : array) {
|
|
||||||
writeSizedString(element);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Writes an {@link UUID}.
|
|
||||||
* It is done by writing both long, the most and least significant bits.
|
|
||||||
*
|
|
||||||
* @param uuid the {@link UUID} to write
|
|
||||||
*/
|
|
||||||
public void writeUuid(@NotNull UUID uuid) {
|
public void writeUuid(@NotNull UUID uuid) {
|
||||||
writeLong(uuid.getMostSignificantBits());
|
this.buffer.write(UUID, uuid);
|
||||||
writeLong(uuid.getLeastSignificantBits());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeBlockPosition(@NotNull Point point) {
|
public void writeBlockPosition(@NotNull Point point) {
|
||||||
@ -300,30 +160,15 @@ public class BinaryWriter extends OutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void writeBlockPosition(int x, int y, int z) {
|
public void writeBlockPosition(int x, int y, int z) {
|
||||||
writeLong(SerializerUtils.positionToLong(x, y, z));
|
this.buffer.write(BLOCK_POSITION, new Vec(x, y, z));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeItemStack(@NotNull ItemStack itemStack) {
|
public void writeItemStack(@NotNull ItemStack itemStack) {
|
||||||
if (itemStack.isAir()) {
|
this.buffer.write(ITEM, itemStack);
|
||||||
writeBoolean(false);
|
|
||||||
} else {
|
|
||||||
writeBoolean(true);
|
|
||||||
writeVarInt(itemStack.material().id());
|
|
||||||
writeByte((byte) itemStack.amount());
|
|
||||||
write(itemStack.meta());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeNBT(@NotNull String name, @NotNull NBT tag) {
|
public void writeNBT(@NotNull String name, @NotNull NBT tag) {
|
||||||
if (nbtWriter == null) {
|
this.buffer.write(NBT, tag);
|
||||||
this.nbtWriter = new NBTWriter(this, CompressedProcesser.NONE);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
nbtWriter.writeNamed(name, tag);
|
|
||||||
} catch (IOException e) {
|
|
||||||
// should not throw, as nbtWriter points to this PacketWriter
|
|
||||||
MinecraftServer.getExceptionManager().handleException(e);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public <L, R> void writeEither(Either<L, R> either, BiConsumer<BinaryWriter, L> leftWriter, BiConsumer<BinaryWriter, R> rightWriter) {
|
public <L, R> void writeEither(Either<L, R> either, BiConsumer<BinaryWriter, L> leftWriter, BiConsumer<BinaryWriter, R> rightWriter) {
|
||||||
@ -346,12 +191,13 @@ public class BinaryWriter extends OutputStream {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void write(@NotNull ByteBuffer buffer) {
|
public void write(@NotNull ByteBuffer buffer) {
|
||||||
ensureSize(buffer.remaining());
|
byte[] remaining = new byte[buffer.remaining()];
|
||||||
this.buffer.put(buffer);
|
buffer.get(remaining);
|
||||||
|
writeBytes(remaining);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void write(@NotNull BinaryWriter writer) {
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
write(writer.buffer);
|
writeBytes(writer.toByteArray());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -387,73 +233,18 @@ public class BinaryWriter extends OutputStream {
|
|||||||
* @return the byte array containing all the {@link BinaryWriter} data
|
* @return the byte array containing all the {@link BinaryWriter} data
|
||||||
*/
|
*/
|
||||||
public byte[] toByteArray() {
|
public byte[] toByteArray() {
|
||||||
buffer.flip();
|
byte[] bytes = new byte[buffer.writeIndex()];
|
||||||
byte[] bytes = new byte[buffer.remaining()];
|
this.buffer.copyTo(0, bytes, 0, bytes.length);
|
||||||
buffer.get(bytes);
|
|
||||||
return bytes;
|
return bytes;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the beginning of this writer.
|
|
||||||
*
|
|
||||||
* @param headerWriter the {@link BinaryWriter} to add at the beginning
|
|
||||||
*/
|
|
||||||
public void writeAtStart(@NotNull BinaryWriter headerWriter) {
|
|
||||||
// Get the buffer of the header
|
|
||||||
final var headerBuf = headerWriter.getBuffer();
|
|
||||||
// Merge both the headerBuf and this buffer
|
|
||||||
final var finalBuffer = concat(headerBuf, buffer);
|
|
||||||
// Change the buffer used by this writer
|
|
||||||
setBuffer(finalBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds a {@link BinaryWriter}'s {@link ByteBuffer} at the end of this writer.
|
|
||||||
*
|
|
||||||
* @param footerWriter the {@link BinaryWriter} to add at the end
|
|
||||||
*/
|
|
||||||
public void writeAtEnd(@NotNull BinaryWriter footerWriter) {
|
|
||||||
// Get the buffer of the footer
|
|
||||||
final var footerBuf = footerWriter.getBuffer();
|
|
||||||
// Merge both this buffer and the footerBuf
|
|
||||||
final var finalBuffer = concat(buffer, footerBuf);
|
|
||||||
// Change the buffer used by this writer
|
|
||||||
setBuffer(finalBuffer);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static ByteBuffer concat(final ByteBuffer... buffers) {
|
|
||||||
final ByteBuffer combined = ByteBuffer.allocate(Arrays.stream(buffers).mapToInt(Buffer::remaining).sum());
|
|
||||||
Arrays.stream(buffers).forEach(b -> combined.put(b.duplicate()));
|
|
||||||
return combined;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the raw buffer used by this binary writer.
|
|
||||||
*
|
|
||||||
* @return the raw buffer
|
|
||||||
*/
|
|
||||||
public @NotNull ByteBuffer getBuffer() {
|
|
||||||
return buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Changes the buffer used by this binary writer.
|
|
||||||
*
|
|
||||||
* @param buffer the new buffer used by this binary writer
|
|
||||||
*/
|
|
||||||
public void setBuffer(ByteBuffer buffer) {
|
|
||||||
this.buffer = buffer;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void write(int b) {
|
public void write(int b) {
|
||||||
writeByte((byte) b);
|
writeByte((byte) b);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void writeUnsignedShort(int yourShort) {
|
public void writeUnsignedShort(int yourShort) {
|
||||||
// FIXME unsigned
|
this.buffer.write(SHORT, (short) (yourShort & 0xFFFF));
|
||||||
ensureSize(Short.BYTES);
|
|
||||||
buffer.putShort((short) (yourShort & 0xFFFF));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
286
src/test/java/net/minestom/server/network/NetworkBufferTest.java
Normal file
286
src/test/java/net/minestom/server/network/NetworkBufferTest.java
Normal file
@ -0,0 +1,286 @@
|
|||||||
|
package net.minestom.server.network;
|
||||||
|
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import org.jetbrains.annotations.UnknownNullability;
|
||||||
|
import org.jglrxavpok.hephaistos.nbt.NBT;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
public class NetworkBufferTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readableBytes() {
|
||||||
|
var buffer = new NetworkBuffer();
|
||||||
|
assertEquals(0, buffer.readableBytes());
|
||||||
|
|
||||||
|
buffer.write(NetworkBuffer.BYTE, (byte) 0);
|
||||||
|
assertEquals(1, buffer.readableBytes());
|
||||||
|
|
||||||
|
buffer.write(NetworkBuffer.LONG, 50L);
|
||||||
|
assertEquals(9, buffer.readableBytes());
|
||||||
|
|
||||||
|
assertEquals((byte) 0, buffer.read(NetworkBuffer.BYTE));
|
||||||
|
assertEquals(8, buffer.readableBytes());
|
||||||
|
|
||||||
|
assertEquals(50L, buffer.read(NetworkBuffer.LONG));
|
||||||
|
assertEquals(0, buffer.readableBytes());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void numbers() {
|
||||||
|
assertBufferType(NetworkBuffer.BOOLEAN, false, new byte[]{0x00});
|
||||||
|
assertBufferType(NetworkBuffer.BOOLEAN, true, new byte[]{0x01});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.BYTE, (byte) 0x00, new byte[]{0x00});
|
||||||
|
assertBufferType(NetworkBuffer.BYTE, (byte) 0x01, new byte[]{0x01});
|
||||||
|
assertBufferType(NetworkBuffer.BYTE, (byte) 0x7F, new byte[]{0x7F});
|
||||||
|
assertBufferType(NetworkBuffer.BYTE, (byte) 0x80, new byte[]{(byte) 0x80});
|
||||||
|
assertBufferType(NetworkBuffer.BYTE, (byte) 0xFF, new byte[]{(byte) 0xFF});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.SHORT, (short) 0x0000, new byte[]{0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.SHORT, (short) 0x0001, new byte[]{0x00, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.SHORT, (short) 0x7FFF, new byte[]{0x7F, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.SHORT, (short) 0x8000, new byte[]{(byte) 0x80, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.SHORT, (short) 0xFFFF, new byte[]{(byte) 0xFF, (byte) 0xFF});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.INT, 0, new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 1, new byte[]{0x00, 0x00, 0x00, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 2, new byte[]{0x00, 0x00, 0x00, 0x02});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 127, new byte[]{0x00, 0x00, 0x00, 0x7F});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 128, new byte[]{0x00, 0x00, 0x00, (byte) 0x80});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 255, new byte[]{0x00, 0x00, 0x00, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 256, new byte[]{0x00, 0x00, 0x01, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 25565, new byte[]{0x00, 0x00, 0x63, (byte) 0xDD});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 32767, new byte[]{0x00, 0x00, 0x7F, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 32768, new byte[]{0x00, 0x00, (byte) 0x80, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 65535, new byte[]{0x00, 0x00, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 65536, new byte[]{0x00, 0x01, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.INT, 2147483647, new byte[]{0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.INT, -1, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.INT, -2147483648, new byte[]{(byte) 0x80, 0x00, 0x00, 0x00});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 0L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 1L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 2L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 127L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 128L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x80});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 255L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 256L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 25565L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x63, (byte) 0xDD});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 32767L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7F, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 32768L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0x80, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 65535L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 65536L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 2147483647L, new byte[]{0x00, 0x00, 0x00, 0x00, 0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 2147483648L, new byte[]{0x00, 0x00, 0x00, 0x00, (byte) 0x80, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 4294967295L, new byte[]{0x00, 0x00, 0x00, 0x00, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 4294967296L, new byte[]{0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, 9223372036854775807L, new byte[]{0x7F, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, -1L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, -2147483648L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0x80, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, -4294967296L, new byte[]{(byte) 0xFF, (byte) 0xFF, (byte) 0xFF, (byte) 0xFF, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.LONG, -9223372036854775808L, new byte[]{(byte) 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 0f, new byte[]{0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 1f, new byte[]{0x3F, (byte) 0x80, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 1.1f, new byte[]{0x3F, (byte) 0x8C, (byte) 0xCC, (byte) 0xCD});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 1.5f, new byte[]{0x3F, (byte) 0xC0, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 1.6f, new byte[]{0x3F, (byte) 0xCC, (byte) 0xCC, (byte) 0xCD});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 2f, new byte[]{0x40, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 2.5f, new byte[]{0x40, 0x20, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 3f, new byte[]{0x40, 0x40, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 4f, new byte[]{0x40, (byte) 0x80, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 5f, new byte[]{0x40, (byte) 0xA0, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 10f, new byte[]{0x41, 0x20, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 100f, new byte[]{0x42, (byte) 0xC8, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 1000f, new byte[]{0x44, 0x7a, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 10000f, new byte[]{0x46, 0x1C, 0x40, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.FLOAT, 100000f, new byte[]{0x47, (byte) 0xC3, 0x50, 0x00});
|
||||||
|
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 0d, new byte[]{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 1d, new byte[]{0x3F, (byte) 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 1.1d, new byte[]{0x3F, (byte) 0xF1, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x9A});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 1.5d, new byte[]{0x3F, (byte) 0xF8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 1.6d, new byte[]{0x3F, (byte) 0xF9, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x99, (byte) 0x9A});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 2d, new byte[]{0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 2.5d, new byte[]{0x40, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 3d, new byte[]{0x40, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 4d, new byte[]{0x40, 0x10, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 5d, new byte[]{0x40, 0x14, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 10d, new byte[]{0x40, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 100d, new byte[]{0x40, 0x59, (byte) 0x00, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 1000d, new byte[]{0x40, (byte) 0x8F, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
assertBufferType(NetworkBuffer.DOUBLE, 10000d, new byte[]{0x40, (byte) 0xC3, (byte) 0x88, 0x00, 0x00, 0x00, 0x00, 0x00});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void varInt() {
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 0, new byte[]{0});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 1, new byte[]{0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 2, new byte[]{0x02});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 11, new byte[]{0x0B});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 127, new byte[]{0x7f});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 128, new byte[]{(byte) 0x80, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 255, new byte[]{(byte) 0xff, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 25565, new byte[]{(byte) 0xdd, (byte) 0xc7, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 2097151, new byte[]{(byte) 0xff, (byte) 0xff, 0x7f});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, 2147483647, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, -1, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x0f});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_INT, -2147483648, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x08});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void varLong() {
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 0L, new byte[]{0});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 1L, new byte[]{0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 2L, new byte[]{0x02});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 127L, new byte[]{0x7f});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 128L, new byte[]{(byte) 0x80, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 255L, new byte[]{(byte) 0xff, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 2147483647L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x07});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, 9223372036854775807L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x7f});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, -1L, new byte[]{(byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, -2147483648L, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0xf8, (byte) 0xff, (byte) 0xff, (byte) 0xff, (byte) 0xff, 0x01});
|
||||||
|
assertBufferType(NetworkBuffer.VAR_LONG, -9223372036854775808L, new byte[]{(byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, (byte) 0x80, 0x01});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void rawBytes() {
|
||||||
|
// FIXME: currently break because the array is identity compared
|
||||||
|
//assertBufferType(NetworkBuffer.RAW_BYTES, new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64},
|
||||||
|
// new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void string() {
|
||||||
|
assertBufferType(NetworkBuffer.STRING, "Hello World", new byte[]{0x0B, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x20, 0x57, 0x6f, 0x72, 0x6c, 0x64});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void nbt() {
|
||||||
|
assertBufferType(NetworkBuffer.NBT, NBT.Int(5));
|
||||||
|
assertBufferType(NetworkBuffer.NBT, NBT.Compound(Map.of("key", NBT.Int(5))));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void component() {
|
||||||
|
assertBufferType(NetworkBuffer.COMPONENT, Component.text("Hello world"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void uuid() {
|
||||||
|
assertBufferType(NetworkBuffer.UUID, new UUID(0, 0), new byte[]{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0});
|
||||||
|
assertBufferType(NetworkBuffer.UUID, new UUID(1, 1), new byte[]{0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void item() {
|
||||||
|
assertBufferType(NetworkBuffer.ITEM, ItemStack.AIR);
|
||||||
|
assertBufferType(NetworkBuffer.ITEM, ItemStack.of(Material.STONE, 1));
|
||||||
|
assertBufferType(NetworkBuffer.ITEM, ItemStack.of(Material.DIAMOND_AXE, 1).withMeta(builder -> builder.damage(1)));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void optional() {
|
||||||
|
assertBufferTypeOptional(NetworkBuffer.BOOLEAN, null, new byte[]{0});
|
||||||
|
assertBufferTypeOptional(NetworkBuffer.BOOLEAN, true, new byte[]{1, 1});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void collection() {
|
||||||
|
assertBufferTypeCollection(NetworkBuffer.BOOLEAN, List.of(), new byte[]{0});
|
||||||
|
assertBufferTypeCollection(NetworkBuffer.BOOLEAN, List.of(true), new byte[]{0x01, 0x01});
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value, byte[] expected, @NotNull Action<T> action) {
|
||||||
|
var buffer = new NetworkBuffer();
|
||||||
|
action.write(buffer, type, value);
|
||||||
|
assertEquals(0, buffer.readIndex());
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.writeIndex());
|
||||||
|
|
||||||
|
var actual = action.read(buffer, type);
|
||||||
|
|
||||||
|
assertEquals(value, actual);
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.readIndex(), "Invalid read index");
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.writeIndex());
|
||||||
|
|
||||||
|
if (expected != null) {
|
||||||
|
var bytes = new byte[expected.length];
|
||||||
|
buffer.copyTo(0, bytes, 0, bytes.length);
|
||||||
|
assertArrayEquals(expected, bytes, "Invalid bytes: " + Arrays.toString(expected) + " != " + Arrays.toString(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @NotNull T value, byte @Nullable [] expected) {
|
||||||
|
assertBufferType(type, value, expected, new Action<>() {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type, @UnknownNullability T value) {
|
||||||
|
buffer.write(type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type) {
|
||||||
|
return buffer.read(type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferType(NetworkBuffer.@NotNull Type<T> type, @NotNull T value) {
|
||||||
|
assertBufferType(type, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferTypeOptional(NetworkBuffer.@NotNull Type<T> type, @Nullable T value, byte @Nullable [] expected) {
|
||||||
|
assertBufferType(type, value, expected, new Action<T>() {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type, @UnknownNullability T value) {
|
||||||
|
buffer.writeOptional(type, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public T read(@NotNull NetworkBuffer buffer, @NotNull NetworkBuffer.Type<T> type) {
|
||||||
|
return buffer.readOptional(type);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferTypeOptional(NetworkBuffer.@NotNull Type<T> type, @Nullable T value) {
|
||||||
|
assertBufferTypeOptional(type, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferTypeCollection(NetworkBuffer.@NotNull Type<T> type, @NotNull Collection<T> values, byte @Nullable [] expected) {
|
||||||
|
var buffer = new NetworkBuffer();
|
||||||
|
buffer.writeCollection(type, values);
|
||||||
|
assertEquals(0, buffer.readIndex());
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.writeIndex());
|
||||||
|
|
||||||
|
var actual = buffer.readCollection(type);
|
||||||
|
|
||||||
|
assertEquals(values, actual);
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.readIndex());
|
||||||
|
if (expected != null) assertEquals(expected.length, buffer.writeIndex());
|
||||||
|
|
||||||
|
if (expected != null) {
|
||||||
|
var bytes = new byte[expected.length];
|
||||||
|
buffer.copyTo(0, bytes, 0, bytes.length);
|
||||||
|
assertArrayEquals(expected, bytes, "Invalid bytes: " + Arrays.toString(expected) + " != " + Arrays.toString(bytes));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static <T> void assertBufferTypeCollection(NetworkBuffer.@NotNull Type<T> type, @NotNull Collection<T> value) {
|
||||||
|
assertBufferTypeCollection(type, value, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Action<T> {
|
||||||
|
void write(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type<T> type, @UnknownNullability T value);
|
||||||
|
|
||||||
|
T read(@NotNull NetworkBuffer buffer, NetworkBuffer.@NotNull Type<T> type);
|
||||||
|
}
|
||||||
|
}
|
107
src/test/java/net/minestom/server/network/SocketWriteTest.java
Normal file
107
src/test/java/net/minestom/server/network/SocketWriteTest.java
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package net.minestom.server.network;
|
||||||
|
|
||||||
|
import net.minestom.server.network.packet.server.ServerPacket;
|
||||||
|
import net.minestom.server.utils.ObjectPool;
|
||||||
|
import net.minestom.server.utils.PacketUtils;
|
||||||
|
import net.minestom.server.utils.Utils;
|
||||||
|
import net.minestom.server.utils.binary.BinaryWriter;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||||
|
|
||||||
|
public class SocketWriteTest {
|
||||||
|
|
||||||
|
record IntPacket(int value) implements ServerPacket {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
|
writer.writeInt(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record CompressiblePacket(String value) implements ServerPacket {
|
||||||
|
@Override
|
||||||
|
public void write(@NotNull BinaryWriter writer) {
|
||||||
|
writer.writeSizedString(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getId() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeSingleUncompressed() {
|
||||||
|
var packet = new IntPacket(5);
|
||||||
|
|
||||||
|
var buffer = ObjectPool.PACKET_POOL.get();
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, false);
|
||||||
|
|
||||||
|
// 3 bytes length [var-int] + 1 byte packet id [var-int] + 4 bytes int
|
||||||
|
// The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
|
||||||
|
assertEquals(3 + 1 + 4, buffer.position(), "Invalid buffer position");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeMultiUncompressed() {
|
||||||
|
var packet = new IntPacket(5);
|
||||||
|
|
||||||
|
var buffer = ObjectPool.PACKET_POOL.get();
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, false);
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, false);
|
||||||
|
|
||||||
|
// 3 bytes length [var-int] + 1 byte packet id [var-int] + 4 bytes int
|
||||||
|
// The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
|
||||||
|
assertEquals((3 + 1 + 4) * 2, buffer.position(), "Invalid buffer position");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeSingleCompressed() {
|
||||||
|
var string = "Hello world!".repeat(200);
|
||||||
|
var stringLength = string.getBytes(StandardCharsets.UTF_8).length;
|
||||||
|
var lengthLength = Utils.getVarIntSize(stringLength);
|
||||||
|
|
||||||
|
var packet = new CompressiblePacket(string);
|
||||||
|
|
||||||
|
var buffer = ObjectPool.PACKET_POOL.get();
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, true);
|
||||||
|
|
||||||
|
// 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + payload
|
||||||
|
// The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
|
||||||
|
assertNotEquals(3 + 3 + 1 + lengthLength + stringLength, buffer.position(), "Buffer position does not account for compression");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeSingleCompressedSmall() {
|
||||||
|
var packet = new IntPacket(5);
|
||||||
|
|
||||||
|
var buffer = ObjectPool.PACKET_POOL.get();
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, true);
|
||||||
|
|
||||||
|
// 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + 4 bytes int
|
||||||
|
// The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
|
||||||
|
assertEquals(3 + 3 + 1 + 4, buffer.position(), "Invalid buffer position");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void writeMultiCompressedSmall() {
|
||||||
|
var packet = new IntPacket(5);
|
||||||
|
|
||||||
|
var buffer = ObjectPool.PACKET_POOL.get();
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, true);
|
||||||
|
PacketUtils.writeFramedPacket(buffer, packet, true);
|
||||||
|
|
||||||
|
// 3 bytes packet length [var-int] + 3 bytes data length [var-int] + 1 byte packet id [var-int] + 4 bytes int
|
||||||
|
// The 3 bytes var-int length is hardcoded for performance purpose, could change in the future
|
||||||
|
assertEquals((3 + 3 + 1 + 4) * 2, buffer.position(), "Invalid buffer position");
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user