diff --git a/build.gradle.kts b/build.gradle.kts index b638aa5..2653373 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -18,8 +18,6 @@ java { repositories { mavenCentral() - maven("https://s01.oss.sonatype.org/content/repositories/snapshots/") - maven("https://jitpack.io") maven("https://maven.enginehub.org/repo/") maven("https://repo.papermc.io/repository/maven-public/") maven("https://repo.viaversion.com") @@ -27,7 +25,6 @@ repositories { dependencies { paperweight.paperDevBundle("1.20.1-R0.1-SNAPSHOT") - implementation("xyz.jpenilla:reflection-remapper:0.1.0-SNAPSHOT") // Zstd Compression Library implementation("com.github.luben:zstd-jni:1.5.5-4") diff --git a/src/main/java/com/moulberry/axiom/AxiomConstants.java b/src/main/java/com/moulberry/axiom/AxiomConstants.java index 11de348..0287fb0 100644 --- a/src/main/java/com/moulberry/axiom/AxiomConstants.java +++ b/src/main/java/com/moulberry/axiom/AxiomConstants.java @@ -7,10 +7,16 @@ public class AxiomConstants { public static final long MIN_POSITION_LONG = 0b1000000000000000000000000010000000000000000000000000100000000000L; public static final int API_VERSION = 5; - public static final NamespacedKey ACTIVE_HOTBAR_INDEX = new NamespacedKey("axiom", "active_hotbar_index"); - public static final NamespacedKey HOTBAR_DATA = new NamespacedKey("axiom", "hotbar_data"); - public static final NamespacedKey ACTIVE_VIEW = new NamespacedKey("axiom", "active_view"); - public static final NamespacedKey VIEWS = new NamespacedKey("axiom", "views"); + public static final String PERMISSION = "axiom.*"; + public static final NamespacedKey ACTIVE_HOTBAR_INDEX = axiomKey("active_hotbar_index"); + public static final NamespacedKey HOTBAR_DATA = axiomKey("hotbar_data"); + + public static final NamespacedKey ACTIVE_VIEW = axiomKey("active_view"); + public static final NamespacedKey VIEWS = axiomKey("views"); + + public static NamespacedKey axiomKey(String key) { + return new NamespacedKey("axiom", key); + } } diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index b187d2b..9172f79 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -2,8 +2,12 @@ package com.moulberry.axiom; import com.moulberry.axiom.integration.PaperFailMoveListener; import com.moulberry.axiom.packet.*; +import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.server.level.ServerPlayer; import org.bukkit.Bukkit; +import org.bukkit.entity.Player; import org.bukkit.event.Listener; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.java.JavaPlugin; @@ -14,19 +18,22 @@ import java.util.logging.Level; public class AxiomPaper extends JavaPlugin implements Listener { - public static final String PERMISSION = "axiom.*"; - private static AxiomPaper instance; public static Plugin getPlugin() { return instance; } private static final Map listeners = new ConcurrentHashMap<>(); - public static AxiomPacketListener getListener(String channel) { return listeners.get(channel); } + private static final Class CraftPlayer = Reflection.getClass("org.bukkit.craftbukkit.entity.CraftPlayer"); + private static final Reflection.Method getHandle = Reflection.getTypedMethod(CraftPlayer, "getHandle", ServerPlayer.class); + public static ServerPlayer convert(Player player) { + return getHandle.invoke(player); + } + @Override public void onEnable() { instance = this; @@ -41,26 +48,28 @@ public class AxiomPaper extends JavaPlugin implements Listener { Bukkit.getMessenger().registerOutgoingPluginChannel(this, channel.getChannel()); } - //TODO multiversion (Except setGamemode) - registerInChannel("hello", new HelloPacketListener()); + registerInChannel("hello", new HelloPacketListener(new AxiomPlayerManager())); registerInChannel("set_gamemode", new SetGamemodePacketListener()); registerInChannel("set_fly_speed", new SetFlySpeedPacketListener()); - registerInChannel("set_block", new SetBlockPacketListener()); + registerInChannel("set_block", new SetBlockPacketListener()); //TODO multiversion registerInChannel("set_hotbar_slot", new SetHotbarSlotPacketListener()); registerInChannel("switch_active_hotbar", new SwitchActiveHotbarPacketListener()); registerInChannel("teleport", new TeleportPacketListener()); registerInChannel("set_editor_views", new SetEditorViewsPacketListener()); registerInChannel("request_block_entity", new RequestBlockEntityPacketListener()); - registerInChannel("handle_big_payload", new SetBlockBufferPacketListener()); + registerInChannel("handle_big_payload", new SetBlockBufferPacketListener()); //TODO multiversion } private void registerInChannel(String channel, AxiomPacketListener handler) { listeners.put(channel, handler); Bukkit.getMessenger().registerIncomingPluginChannel(this, "axiom:" + channel, (ch, player, message) -> { - if (!player.hasPermission(PERMISSION)) + ByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); + if (!player.hasPermission(AxiomConstants.PERMISSION)) { + handler.onMissingPerm(player, buf); return; + } - handler.onMessage(player, Unpooled.wrappedBuffer(message)); + handler.onMessage(player, buf); }); } diff --git a/src/main/java/com/moulberry/axiom/AxiomPlayerManager.java b/src/main/java/com/moulberry/axiom/AxiomPlayerManager.java new file mode 100644 index 0000000..23d6c4b --- /dev/null +++ b/src/main/java/com/moulberry/axiom/AxiomPlayerManager.java @@ -0,0 +1,38 @@ +package com.moulberry.axiom; + +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerQuitEvent; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +public class AxiomPlayerManager implements Listener { + + private final Set axiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + + public AxiomPlayerManager() { + Bukkit.getPluginManager().registerEvents(this, AxiomPaper.getPlugin()); + Bukkit.getScheduler().scheduleSyncRepeatingTask(AxiomPaper.getPlugin(), () -> axiomPlayers.removeIf(player -> { + if (!player.hasPermission(AxiomConstants.PERMISSION)) { + OutChannel.ENABLE.send(player, new byte[] { 0 }); + return true; + } + + return false; + }), 20, 20); + } + + public void add(Player player) { + axiomPlayers.add(player); + } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + axiomPlayers.remove(event.getPlayer()); + } + +} diff --git a/src/main/java/com/moulberry/axiom/Reflection.java b/src/main/java/com/moulberry/axiom/Reflection.java new file mode 100644 index 0000000..a3929b4 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/Reflection.java @@ -0,0 +1,137 @@ +package com.moulberry.axiom; + +import org.bukkit.Bukkit; + +import java.lang.reflect.*; +import java.util.Arrays; + +public class Reflection { + private Reflection() {} + + public interface Field { + R get(T target); + void set(T target, R value); + } + + public static Field getField(Class target, Class fieldType, Class... parameters) { + return getField(target, fieldType, 0, parameters); + } + + public static Field getField(Class target, Class fieldType, int index, Class... parameters) { + for (final java.lang.reflect.Field field : target.getDeclaredFields()) { + if(matching(field, fieldType, parameters) && index-- <= 0) { + field.setAccessible(true); + + return new Field<>() { + @Override + @SuppressWarnings("unchecked") + public R get(T target) { + try { + return (R) field.get(target); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access reflection.", e); + } + } + + @Override + public void set(T target, R value) { + try { + field.set(target, value); + } catch (IllegalAccessException e) { + throw new IllegalArgumentException("Cannot access reflection.", e); + } + } + }; + } + } + + if (target.getSuperclass() != null) { + try { + return getField((Class) target.getSuperclass(), fieldType, index, parameters); + } catch (ClassCastException e) {} + } + + throw new IllegalArgumentException("Cannot find field with type " + fieldType); + } + + private static boolean matching(java.lang.reflect.Field field, Class fieldType, Class... parameters) { + if(!fieldType.isAssignableFrom(field.getType())) + return false; + + if(parameters.length > 0) { + Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments(); + + for(int i = 0; i < parameters.length; i++) { + if(arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i]) + return false; + } + } + return true; + } + + + public interface Method { + R invoke(T target, Object... arguments); + } + + public static Method getMethod(Class clazz, Class... params) { + return getTypedMethod(clazz, null, null, 0, params); + } + + public static Method getMethod(Class clazz, String methodName, Class... params) { + return getTypedMethod(clazz, methodName, null, 0, params); + } + + public static Method getTypedMethod(Class clazz, Class returnType, Class... params) { + return getTypedMethod(clazz, null, returnType, 0, params); + } + + public static Method getTypedMethod(Class clazz, Class returnType, int index, Class... params) { + return getTypedMethod(clazz, null, returnType, index, params); + } + + public static Method getTypedMethod(Class clazz, String methodName, Class returnType, Class... params) { + return getTypedMethod(clazz, methodName, returnType, 0, params); + } + + private static Method getTypedMethod(Class clazz, String methodName, Class returnType, int index, Class... params) { + for (final java.lang.reflect.Method method : clazz.getDeclaredMethods()) { + if ((methodName == null || method.getName().equals(methodName)) + && (returnType == null || method.getReturnType().equals(returnType)) + && Arrays.equals(method.getParameterTypes(), params) + && index-- <= 0) { + method.setAccessible(true); + + return (target, arguments) -> { + try { + return (R) method.invoke(target, arguments); + } catch (Exception e) { + throw new IllegalArgumentException("Cannot invoke method " + method, e); + } + }; + } + } + + if (clazz.getSuperclass() != null) { + try { + return getTypedMethod((Class) clazz.getSuperclass(), methodName, returnType, index, params); + } catch (ClassCastException e) {} + } + + throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params))); + } + + + private static final String ORG_BUKKIT_CRAFTBUKKIT = Bukkit.getServer().getClass().getPackage().getName(); + public static Class getClass(String name) { + if(name.startsWith("org.bukkit.craftbukkit")) + name = ORG_BUKKIT_CRAFTBUKKIT + name.substring(22); + + try { + return (Class) Class.forName(name); + } catch (ClassNotFoundException e) { + throw new IllegalArgumentException("Cannot find " + name, e); + } + } + +} diff --git a/src/main/java/com/moulberry/axiom/View.java b/src/main/java/com/moulberry/axiom/View.java index 7d219dd..16b6f74 100644 --- a/src/main/java/com/moulberry/axiom/View.java +++ b/src/main/java/com/moulberry/axiom/View.java @@ -1,13 +1,12 @@ package com.moulberry.axiom; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.persistence.UUIDDataType; -import net.minecraft.core.registries.Registries; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; +import io.netty.buffer.ByteBuf; +import org.bukkit.Bukkit; +import org.bukkit.Location; import org.bukkit.NamespacedKey; +import org.bukkit.World; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -15,73 +14,76 @@ import java.util.UUID; public final class View { - public String name; - public final UUID uuid; - public boolean pinLevel = false; - public boolean pinLocation = false; - private ResourceKey level = null; - private Vec3 position = null; - private float yaw; - private float pitch; + private final String name; + private final UUID uuid; + private boolean pinLevel = false; + private boolean pinLocation = false; + private World level = null; + private Location location = null; public View(String name, UUID uuid) { this.name = name; this.uuid = uuid; } - public void write(FriendlyByteBuf byteBuf) { - byteBuf.writeUtf(this.name, 64); - byteBuf.writeUUID(this.uuid); + public void write(ByteBuf buf) { + MojBuf.writeUtf(buf, this.name, 64); + MojBuf.writeUUID(buf, this.uuid); - byteBuf.writeBoolean(this.pinLevel); + buf.writeBoolean(this.pinLevel); if (this.pinLevel && this.level != null) { - byteBuf.writeBoolean(true); - byteBuf.writeResourceKey(this.level); + buf.writeBoolean(true); + MojBuf.writeKey(buf, this.level.getKey()); } else { - byteBuf.writeBoolean(false); + buf.writeBoolean(false); } - byteBuf.writeBoolean(this.pinLocation); - if (this.position != null) { - byteBuf.writeBoolean(true); - byteBuf.writeDouble(this.position.x); - byteBuf.writeDouble(this.position.y); - byteBuf.writeDouble(this.position.z); - byteBuf.writeFloat(this.yaw); - byteBuf.writeFloat(this.pitch); + buf.writeBoolean(this.pinLocation); + if (this.location != null) { + buf.writeBoolean(true); + buf.writeDouble(this.location.getX()); + buf.writeDouble(this.location.getY()); + buf.writeDouble(this.location.getZ()); + buf.writeFloat(this.location.getYaw()); + buf.writeFloat(this.location.getPitch()); } else { - byteBuf.writeBoolean(false); + buf.writeBoolean(false); } } - public static View read(FriendlyByteBuf byteBuf) { - View view = new View(byteBuf.readUtf(64), byteBuf.readUUID()); + public static View read(ByteBuf buf) { + View view = new View(MojBuf.readUtf(buf, 64), MojBuf.readUUID(buf)); - view.pinLevel = byteBuf.readBoolean(); - if (byteBuf.readBoolean()) { - view.level = byteBuf.readResourceKey(Registries.DIMENSION); + view.pinLevel = buf.readBoolean(); + if (buf.readBoolean()) { + view.level = Bukkit.getWorld(MojBuf.readKey(buf)); } - view.pinLocation = byteBuf.readBoolean(); - if (byteBuf.readBoolean()) { - view.position = new Vec3(byteBuf.readDouble(), byteBuf.readDouble(), byteBuf.readDouble()); - view.yaw = byteBuf.readFloat(); - view.pitch = byteBuf.readFloat(); + view.pinLocation = buf.readBoolean(); + if (buf.readBoolean()) { + view.location = new Location(view.level, + buf.readDouble(), + buf.readDouble(), + buf.readDouble(), + buf.readFloat(), + buf.readFloat() + ); } return view; } - private static final NamespacedKey NAME_KEY = new NamespacedKey("axiom", "view_name"); - private static final NamespacedKey UUID_KEY = new NamespacedKey("axiom", "view_uuid"); - private static final NamespacedKey PIN_LEVEL_KEY = new NamespacedKey("axiom", "view_pin_level"); - private static final NamespacedKey LEVEL_KEY = new NamespacedKey("axiom", "view_level"); - private static final NamespacedKey PIN_LOCATION_KEY = new NamespacedKey("axiom", "view_pin_location"); - private static final NamespacedKey X_KEY = new NamespacedKey("axiom", "view_x"); - private static final NamespacedKey Y_KEY = new NamespacedKey("axiom", "view_y"); - private static final NamespacedKey Z_KEY = new NamespacedKey("axiom", "view_z"); - private static final NamespacedKey YAW_KEY = new NamespacedKey("axiom", "view_yaw"); - private static final NamespacedKey PITCH_KEY = new NamespacedKey("axiom", "view_pitch"); + + private static final NamespacedKey NAME_KEY = AxiomConstants.axiomKey("view_name"); + private static final NamespacedKey UUID_KEY = AxiomConstants.axiomKey("view_uuid"); + private static final NamespacedKey PIN_LEVEL_KEY = AxiomConstants.axiomKey("view_pin_level"); + private static final NamespacedKey LEVEL_KEY = AxiomConstants.axiomKey("view_level"); + private static final NamespacedKey PIN_LOCATION_KEY = AxiomConstants.axiomKey("view_pin_location"); + private static final NamespacedKey X_KEY = AxiomConstants.axiomKey("view_x"); + private static final NamespacedKey Y_KEY = AxiomConstants.axiomKey("view_y"); + private static final NamespacedKey Z_KEY = AxiomConstants.axiomKey("view_z"); + private static final NamespacedKey YAW_KEY = AxiomConstants.axiomKey("view_yaw"); + private static final NamespacedKey PITCH_KEY = AxiomConstants.axiomKey("view_pitch"); public void save(PersistentDataContainer container) { container.set(NAME_KEY, PersistentDataType.STRING, this.name); @@ -89,16 +91,16 @@ public final class View { container.set(PIN_LEVEL_KEY, PersistentDataType.BOOLEAN, this.pinLevel); if (this.pinLevel && this.level != null) { - container.set(LEVEL_KEY, PersistentDataType.STRING, this.level.location().toString()); + container.set(LEVEL_KEY, PersistentDataType.STRING, this.level.getKey().asString()); } container.set(PIN_LOCATION_KEY, PersistentDataType.BOOLEAN, this.pinLocation); - if (this.position != null) { - container.set(X_KEY, PersistentDataType.DOUBLE, this.position.x); - container.set(Y_KEY, PersistentDataType.DOUBLE, this.position.y); - container.set(Z_KEY, PersistentDataType.DOUBLE, this.position.z); - container.set(YAW_KEY, PersistentDataType.FLOAT, this.yaw); - container.set(PITCH_KEY, PersistentDataType.FLOAT, this.pitch); + if (this.location != null) { + container.set(X_KEY, PersistentDataType.DOUBLE, this.location.getX()); + container.set(Y_KEY, PersistentDataType.DOUBLE, this.location.getY()); + container.set(Z_KEY, PersistentDataType.DOUBLE, this.location.getZ()); + container.set(YAW_KEY, PersistentDataType.FLOAT, this.location.getYaw()); + container.set(PITCH_KEY, PersistentDataType.FLOAT, this.location.getPitch()); } } @@ -111,17 +113,18 @@ public final class View { view.pinLevel = tag.getOrDefault(PIN_LEVEL_KEY, PersistentDataType.BOOLEAN, false); if (tag.has(LEVEL_KEY)) { String level = tag.get(LEVEL_KEY, PersistentDataType.STRING); - view.level = ResourceKey.create(Registries.DIMENSION, new ResourceLocation(level)); + view.level = Bukkit.getWorld(NamespacedKey.fromString(level)); } view.pinLocation = tag.getOrDefault(PIN_LOCATION_KEY, PersistentDataType.BOOLEAN, false); if (tag.has(X_KEY) && tag.has(Y_KEY) && tag.has(Z_KEY)) { - double x = tag.getOrDefault(X_KEY, PersistentDataType.DOUBLE, 0.0); - double y = tag.getOrDefault(Y_KEY, PersistentDataType.DOUBLE, 0.0); - double z = tag.getOrDefault(Z_KEY, PersistentDataType.DOUBLE, 0.0); - view.position = new Vec3(x, y, z); - view.yaw = tag.getOrDefault(YAW_KEY, PersistentDataType.FLOAT, 0.0f); - view.pitch = tag.getOrDefault(PITCH_KEY, PersistentDataType.FLOAT, 0.0f); + view.location = new Location(view.level, + tag.getOrDefault(X_KEY, PersistentDataType.DOUBLE, 0.0), + tag.getOrDefault(Y_KEY, PersistentDataType.DOUBLE, 0.0), + tag.getOrDefault(Z_KEY, PersistentDataType.DOUBLE, 0.0), + tag.getOrDefault(YAW_KEY, PersistentDataType.FLOAT, 0.0f), + tag.getOrDefault(PITCH_KEY, PersistentDataType.FLOAT, 0.0f) + ); } return view; diff --git a/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java index c68cda4..0415614 100644 --- a/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java +++ b/src/main/java/com/moulberry/axiom/buffer/BiomeBuffer.java @@ -1,5 +1,6 @@ package com.moulberry.axiom.buffer; +import io.netty.buffer.ByteBuf; import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.resources.ResourceKey; @@ -15,14 +16,14 @@ public class BiomeBuffer { this.palette = palette; } - public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) { - int paletteSize = friendlyByteBuf.readByte(); + public static BiomeBuffer load(ByteBuf buf) { + int paletteSize = buf.readByte(); ResourceKey[] palette = new ResourceKey[255]; for (int i = 0; i < paletteSize; i++) { - ResourceKey key = friendlyByteBuf.readResourceKey(Registries.BIOME); + ResourceKey key = buf.readResourceKey(Registries.BIOME); palette[i] = key; } - Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf); + Position2ByteMap map = Position2ByteMap.load(buf); return new BiomeBuffer(map, palette); } diff --git a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java index 7d948a9..96e9900 100644 --- a/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java +++ b/src/main/java/com/moulberry/axiom/buffer/BlockBuffer.java @@ -1,6 +1,7 @@ package com.moulberry.axiom.buffer; import com.moulberry.axiom.AxiomConstants; +import io.netty.buffer.ByteBuf; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import it.unimi.dsi.fastutil.objects.ObjectSet; @@ -27,22 +28,22 @@ public class BlockBuffer { this.values = new Long2ObjectOpenHashMap<>(); } - public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) { + public static BlockBuffer load(ByteBuf buf) { BlockBuffer buffer = new BlockBuffer(); while (true) { - long index = friendlyByteBuf.readLong(); + long index = buf.readLong(); if (index == AxiomConstants.MIN_POSITION_LONG) break; PalettedContainer palettedContainer = buffer.getOrCreateSection(index); - palettedContainer.read(friendlyByteBuf); + palettedContainer.read((FriendlyByteBuf) buf); - int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt()); + int blockEntitySize = Math.min(4096, MojBuf.readVarInt(buf)); if (blockEntitySize > 0) { Short2ObjectMap map = new Short2ObjectOpenHashMap<>(blockEntitySize); for (int i = 0; i < blockEntitySize; i++) { - short offset = friendlyByteBuf.readShort(); - CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf); + short offset = buf.readShort(); + CompressedBlockEntity blockEntity = CompressedBlockEntity.read(buf); map.put(offset, blockEntity); } buffer.blockEntities.put(index, map); diff --git a/src/main/java/com/moulberry/axiom/buffer/CompressedBlockEntity.java b/src/main/java/com/moulberry/axiom/buffer/CompressedBlockEntity.java index f57e400..c759301 100644 --- a/src/main/java/com/moulberry/axiom/buffer/CompressedBlockEntity.java +++ b/src/main/java/com/moulberry/axiom/buffer/CompressedBlockEntity.java @@ -3,9 +3,10 @@ package com.moulberry.axiom.buffer; import com.github.luben.zstd.Zstd; import com.github.luben.zstd.ZstdDictCompress; import com.github.luben.zstd.ZstdDictDecompress; +import com.moulberry.axiom.Reflection; +import io.netty.buffer.ByteBuf; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.NbtIo; -import net.minecraft.network.FriendlyByteBuf; import java.io.*; import java.util.Objects; @@ -24,41 +25,35 @@ public record CompressedBlockEntity(int originalSize, byte compressionDict, byte } } + private static final Reflection.Method write = Reflection.getMethod(NbtIo.class, CompoundTag.class, DataOutput.class); public static CompressedBlockEntity compress(CompoundTag tag, ByteArrayOutputStream baos) { - try { - baos.reset(); - DataOutputStream dos = new DataOutputStream(baos); - NbtIo.write(tag, dos); - byte[] uncompressed = baos.toByteArray(); - byte[] compressed = Zstd.compress(uncompressed, zstdDictCompress); - return new CompressedBlockEntity(uncompressed.length, (byte) 0, compressed); - } catch (IOException e) { - throw new RuntimeException(e); - } + baos.reset(); + DataOutputStream dos = new DataOutputStream(baos); + write.invoke(null, tag, dos); + byte[] uncompressed = baos.toByteArray(); + byte[] compressed = Zstd.compress(uncompressed, zstdDictCompress); + return new CompressedBlockEntity(uncompressed.length, (byte) 0, compressed); } + private static final Reflection.Method read = Reflection.getTypedMethod(NbtIo.class, CompoundTag.class, DataInput.class); public CompoundTag decompress() { if (this.compressionDict != 0) throw new UnsupportedOperationException("Unknown compression dict: " + this.compressionDict); - try { - byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize); - return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt))); - } catch (IOException e) { - throw new RuntimeException(e); - } + byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize); + return read.invoke(null, new DataInputStream(new ByteArrayInputStream(nbt))); } - public static CompressedBlockEntity read(FriendlyByteBuf friendlyByteBuf) { - int originalSize = friendlyByteBuf.readVarInt(); - byte compressionDict = friendlyByteBuf.readByte(); - byte[] compressed = friendlyByteBuf.readByteArray(); + public static CompressedBlockEntity read(ByteBuf buf) { + int originalSize = MojBuf.readVarInt(buf); + byte compressionDict = buf.readByte(); + byte[] compressed = MojBuf.readByteArray(buf); return new CompressedBlockEntity(originalSize, compressionDict, compressed); } - public void write(FriendlyByteBuf friendlyByteBuf) { - friendlyByteBuf.writeVarInt(this.originalSize); - friendlyByteBuf.writeByte(this.compressionDict); - friendlyByteBuf.writeByteArray(this.compressed); + public void write(ByteBuf buf) { + MojBuf.writeVarInt(buf, this.originalSize); + buf.writeByte(this.compressionDict); + MojBuf.writeByteArray(buf, this.compressed); } } diff --git a/src/main/java/com/moulberry/axiom/buffer/MojBuf.java b/src/main/java/com/moulberry/axiom/buffer/MojBuf.java new file mode 100644 index 0000000..ad4c60b --- /dev/null +++ b/src/main/java/com/moulberry/axiom/buffer/MojBuf.java @@ -0,0 +1,116 @@ +package com.moulberry.axiom.buffer; + +import com.moulberry.axiom.Reflection; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; +import java.util.function.Function; + +public class MojBuf { + private MojBuf() {} + + public static ByteBuf unpooled() { + return new FriendlyByteBuf(Unpooled.buffer()); + } + + private static final Reflection.Method readUtf = Reflection.getTypedMethod(FriendlyByteBuf.class, String.class, int.class); + public static String readUtf(ByteBuf buf, int maxLength) { + return readUtf.invoke((FriendlyByteBuf) buf, maxLength); + } + + public static String readUtf(ByteBuf buf) { + return readUtf(buf, Short.MAX_VALUE); + } + + public static NamespacedKey readKey(ByteBuf buf) { + return NamespacedKey.fromString(readUtf(buf)); + } + + private static final Reflection.Method writeUtf = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, String.class, int.class); + public static void writeUtf(ByteBuf buf, String string, int maxLength) { + writeUtf.invoke((FriendlyByteBuf) buf, string, maxLength); + } + + public static void writeUtf(ByteBuf buf, String string) { + writeUtf(buf, string, Short.MAX_VALUE); + } + + public static void writeKey(ByteBuf buf, NamespacedKey key) { + writeUtf(buf, key.asString()); + } + + // is the first method returning an int with no arguments in 1.20.1 + private static final Reflection.Method readVarInt = Reflection.getTypedMethod(FriendlyByteBuf.class, int.class); + public static int readVarInt(ByteBuf buf) { + return readVarInt.invoke((FriendlyByteBuf) buf); + } + + private static final Reflection.Method writeVarInt = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, int.class); + public static void writeVarInt(ByteBuf buf, int value) { + writeVarInt.invoke((FriendlyByteBuf) buf, value); + } + + + private static final Reflection.Method readNbt = Reflection.getTypedMethod(FriendlyByteBuf.class, CompoundTag.class); + public static CompoundTag readNbt(ByteBuf buf) { + return readNbt.invoke((FriendlyByteBuf) buf); + } + + private static final Reflection.Method readUUID = Reflection.getTypedMethod(FriendlyByteBuf.class, UUID.class); + public static UUID readUUID(ByteBuf buf) { + return readUUID.invoke((FriendlyByteBuf) buf); + } + + private static final Reflection.Method writeUUID = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, UUID.class); + public static void writeUUID(ByteBuf buf, UUID uuid) { + writeUUID.invoke((FriendlyByteBuf) buf, uuid); + } + + private static final Class CraftItemStack = Reflection.getClass("org.bukkit.craftbukkit.inventory.CraftItemStack"); + private static final Reflection.Method asCraftMirror = Reflection.getTypedMethod(CraftItemStack, "asCraftMirror", CraftItemStack, net.minecraft.world.item.ItemStack.class); + public static ItemStack toBukkit(net.minecraft.world.item.ItemStack stack) { + return asCraftMirror.invoke(null, stack); + } + private static final Reflection.Method asNMSCopy = Reflection.getTypedMethod(CraftItemStack, net.minecraft.world.item.ItemStack.class, ItemStack.class); + public static net.minecraft.world.item.ItemStack toMojang(ItemStack stack) { + return asNMSCopy.invoke(null, stack); + } + + private static final Reflection.Method readItem = Reflection.getTypedMethod(FriendlyByteBuf.class, net.minecraft.world.item.ItemStack.class); + public static ItemStack readItem(ByteBuf buf) { + return toBukkit(readItem.invoke((FriendlyByteBuf) buf)); + } + + private static final Reflection.Method writeItem = Reflection.getTypedMethod(FriendlyByteBuf.class, FriendlyByteBuf.class, net.minecraft.world.item.ItemStack.class); + public static void writeItem(ByteBuf buf, ItemStack stack) { + writeItem.invoke((FriendlyByteBuf) buf, toMojang(stack)); + } + + public static byte[] readByteArray(ByteBuf buf) { + byte[] array = new byte[readVarInt(buf)]; + buf.readBytes(array); + return array; + } + + public static void writeByteArray(ByteBuf buf, byte[] array) { + writeVarInt(buf, array.length); + buf.writeBytes(array); + } + + public static List readList(ByteBuf buf, Function reader) { + int length = readVarInt(buf); + + List list = new ArrayList<>(length); + for(int i = 0; i < length; i++) + list.add(reader.apply(buf)); + + return list; + } +} diff --git a/src/main/java/com/moulberry/axiom/integration/PaperFailMoveListener.java b/src/main/java/com/moulberry/axiom/integration/PaperFailMoveListener.java index ebb15a8..f9c9deb 100644 --- a/src/main/java/com/moulberry/axiom/integration/PaperFailMoveListener.java +++ b/src/main/java/com/moulberry/axiom/integration/PaperFailMoveListener.java @@ -1,6 +1,6 @@ package com.moulberry.axiom.integration; -import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.AxiomConstants; import io.papermc.paper.event.player.PlayerFailMoveEvent; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -9,7 +9,7 @@ public class PaperFailMoveListener implements Listener { @EventHandler public void onFailMove(PlayerFailMoveEvent event) { - if (event.getPlayer().hasPermission(AxiomPaper.PERMISSION) && + if (event.getPlayer().hasPermission(AxiomConstants.PERMISSION) && event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) { event.setAllowed(true); } diff --git a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java index 48ef9e0..0304d62 100644 --- a/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java +++ b/src/main/java/com/moulberry/axiom/packet/AxiomBigPayloadHandler.java @@ -1,6 +1,9 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.buffer.MojBuf; +import com.moulberry.axiom.Reflection; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelHandler; import io.netty.channel.ChannelHandlerContext; @@ -8,25 +11,36 @@ import io.netty.channel.ChannelInboundHandlerAdapter; import io.papermc.paper.network.ConnectionEvent; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket; -import net.minecraft.resources.ResourceLocation; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; import org.bukkit.entity.Player; +import org.bukkit.scheduler.BukkitTask; import org.jetbrains.annotations.NotNull; +import java.util.ArrayList; +import java.util.List; + @ChannelHandler.Sharable public class AxiomBigPayloadHandler extends ChannelInboundHandlerAdapter { - private static final int PLUGINMESSAGE_PACKETID = ConnectionProtocol.PLAY.getPacketId(PacketFlow.SERVERBOUND, new ServerboundCustomPayloadPacket(null, null)); + private static final Reflection.Method getPacketId = Reflection.getTypedMethod(ConnectionProtocol.class, int.class, PacketFlow.class, Packet.class); + private static final int PLUGINMESSAGE_PACKETID = getPacketId.invoke(ConnectionProtocol.PLAY, PacketFlow.SERVERBOUND, new ServerboundCustomPayloadPacket(null, null)); private final Player player; + private final List packets = new ArrayList<>(); + private final BukkitTask task; + public AxiomBigPayloadHandler(Player player) { this.player = player; + this.task = Bukkit.getScheduler().runTaskTimer(AxiomPaper.getPlugin(), this::runHandlers, 1, 1); } @Override - public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) throws Exception { + public void channelRead(@NotNull ChannelHandlerContext ctx, @NotNull Object msg) { if(!(msg instanceof ByteBuf in)) { ctx.fireChannelRead(msg); return; @@ -35,15 +49,13 @@ public class AxiomBigPayloadHandler extends ChannelInboundHandlerAdapter { int readerIndexBackup = in.readerIndex(); if(in.readableBytes() != 0) { - FriendlyByteBuf buf = new FriendlyByteBuf((ByteBuf) msg); - - if (buf.readVarInt() == PLUGINMESSAGE_PACKETID) { - ResourceLocation identifier = buf.readResourceLocation(); - if (identifier.getNamespace().equals("axiom") && player.hasPermission(AxiomPaper.PERMISSION)) { - AxiomPacketListener listener = AxiomPaper.getListener(identifier.getPath()); - //TODO sync! - listener.onMessage(player, in); - in.release(); + ByteBuf buf = new FriendlyByteBuf(in); + if (MojBuf.readVarInt(buf) == PLUGINMESSAGE_PACKETID) { + NamespacedKey id = MojBuf.readKey(buf); + if (id.getNamespace().equals("axiom") && player.hasPermission(AxiomConstants.PERMISSION)) { + synchronized (packets) { + packets.add(new QueuedPacket(id.getKey(), buf)); + } return; } } @@ -62,4 +74,29 @@ public class AxiomBigPayloadHandler extends ChannelInboundHandlerAdapter { super.userEventTriggered(ctx, evt); } + @Override + public void channelInactive(@NotNull ChannelHandlerContext ctx) { + if(!task.isCancelled()) + task.cancel(); + + ctx.fireChannelInactive(); + } + + private void runHandlers() { + List queue; + synchronized (packets) { + if(packets.isEmpty()) + return; + + queue = new ArrayList<>(packets); + packets.clear(); + } + + for(QueuedPacket packet : queue) { + AxiomPaper.getListener(packet.name).onMessage(player, packet.in); + packet.in.release(); + } + } + + private record QueuedPacket(String name, ByteBuf in) {} } diff --git a/src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java b/src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java index 1c4dc71..6b151d1 100644 --- a/src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/AxiomPacketListener.java @@ -7,4 +7,6 @@ public interface AxiomPacketListener { void onMessage(Player player, ByteBuf buf); + default void onMissingPerm(Player player, ByteBuf buf) {} + } diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 1b2f7fd..785755f 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -1,60 +1,42 @@ package com.moulberry.axiom.packet; -import com.moulberry.axiom.AxiomConstants; -import com.moulberry.axiom.AxiomPaper; -import com.moulberry.axiom.OutChannel; -import com.moulberry.axiom.View; +import com.moulberry.axiom.*; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomHandshakeEvent; import com.moulberry.axiom.persistence.ItemStackDataType; import com.moulberry.axiom.persistence.UUIDDataType; import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; +import io.netty.channel.Channel; import net.kyori.adventure.text.Component; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.Connection; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.server.network.ServerGamePacketListenerImpl; import org.bukkit.Bukkit; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; +import org.bukkit.Material; import org.bukkit.entity.Player; -import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; -import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; -import java.util.Collections; -import java.util.Set; import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; public class HelloPacketListener implements AxiomPacketListener, Listener { - private final Set axiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + private static final Reflection.Field serverPlayerConnection = Reflection.getField(ServerPlayer.class, ServerGamePacketListenerImpl.class); + private static final Reflection.Field packetListenerConnection = Reflection.getField(ServerGamePacketListenerImpl.class, Connection.class); + private static final Reflection.Field channel = Reflection.getField(Connection.class, Channel.class); - public HelloPacketListener() { - //TODO cleanup (side effects of class) - Bukkit.getPluginManager().registerEvents(this, AxiomPaper.getPlugin()); - Bukkit.getScheduler().scheduleSyncRepeatingTask(AxiomPaper.getPlugin(), () -> axiomPlayers.removeIf(player -> { - if (!player.hasPermission(AxiomPaper.PERMISSION)) { - OutChannel.ENABLE.send(player, new byte[] { 0 }); - return true; - } + private final AxiomPlayerManager axiomPlayers; - return false; - }), 20, 20); - } - - @EventHandler - public void onPlayerQuit(PlayerQuitEvent event) { - axiomPlayers.remove(event.getPlayer()); + public HelloPacketListener(AxiomPlayerManager axiomPlayers) { + this.axiomPlayers = axiomPlayers; } @Override public void onMessage(Player player, ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - int apiVersion = friendlyByteBuf.readVarInt(); - friendlyByteBuf.readNbt(); // Discard + int apiVersion = MojBuf.readVarInt(buf); + MojBuf.readNbt(buf); // Discard if (apiVersion != AxiomConstants.API_VERSION) { player.sendMessage(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + ", while client is " + apiVersion)); @@ -69,18 +51,24 @@ public class HelloPacketListener implements AxiomPacketListener, Listener { } axiomPlayers.add(player); - //TODO thread safety - ((CraftPlayer)player).getHandle().connection.connection.channel.pipeline().addBefore("decoder", "axiom-big-payload-handler", new AxiomBigPayloadHandler(player)); + // Welcome to multiversioning! + channel.get( + packetListenerConnection.get( + serverPlayerConnection.get( + AxiomPaper.convert(player) + ) + ) + ).pipeline().addBefore("decoder", "axiom-big-payload-handler", new AxiomBigPayloadHandler(player)); // Enable - FriendlyByteBuf out = new FriendlyByteBuf(Unpooled.buffer()); + ByteBuf out = MojBuf.unpooled(); out.writeBoolean(true); out.writeByte(0); // todo: world properties out.writeInt(handshakeEvent.getMaxBufferSize()); // Max Buffer Size out.writeBoolean(false); // No source info out.writeBoolean(false); // No source settings - out.writeVarInt(5); // Maximum Reach - out.writeVarInt(16); // Max editor views + MojBuf.writeVarInt(buf, 5); // Maximum Reach + MojBuf.writeVarInt(buf, 16); // Max editor views out.writeBoolean(true); // Editable Views OutChannel.ENABLE.send(player, out); @@ -89,15 +77,15 @@ public class HelloPacketListener implements AxiomPacketListener, Listener { int activeHotbarIndex = container.getOrDefault(AxiomConstants.ACTIVE_HOTBAR_INDEX, PersistentDataType.BYTE, (byte) 0); PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER); if (hotbarItems != null) { - out = new FriendlyByteBuf(Unpooled.buffer()); + out = MojBuf.unpooled(); out.writeByte((byte) activeHotbarIndex); for (int i=0; i<9*9; i++) { // Ignore selected hotbar if (i / 9 == activeHotbarIndex) { - out.writeItem(net.minecraft.world.item.ItemStack.EMPTY); + MojBuf.writeItem(out, new ItemStack(Material.AIR, 0)); } else { - ItemStack stack = hotbarItems.get(new NamespacedKey("axiom", "slot_"+i), ItemStackDataType.INSTANCE); - out.writeItem(CraftItemStack.asNMSCopy(stack)); + ItemStack stack = hotbarItems.get(AxiomConstants.axiomKey("slot_"+i), ItemStackDataType.INSTANCE); + MojBuf.writeItem(out, stack); } } OutChannel.INITIALIZE_HOTBARS.send(player, out); @@ -106,11 +94,11 @@ public class HelloPacketListener implements AxiomPacketListener, Listener { // Initialize Views UUID activeView = container.get(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE); if (activeView != null) { - out = new FriendlyByteBuf(Unpooled.buffer()); - out.writeUUID(activeView); + out = MojBuf.unpooled(); + MojBuf.writeUUID(buf, activeView); PersistentDataContainer[] views = container.get(AxiomConstants.VIEWS, PersistentDataType.TAG_CONTAINER_ARRAY); - out.writeVarInt(views.length); + MojBuf.writeVarInt(out, views.length); for (PersistentDataContainer view : views) { View.load(view).write(out); } diff --git a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java index fc224c5..9712ef1 100644 --- a/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/RequestBlockEntityPacketListener.java @@ -1,22 +1,19 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.OutChannel; +import com.moulberry.axiom.Reflection; import com.moulberry.axiom.buffer.CompressedBlockEntity; +import com.moulberry.axiom.buffer.MojBuf; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap; import net.minecraft.core.BlockPos; -import net.minecraft.core.registries.Registries; import net.minecraft.nbt.CompoundTag; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerLevel; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.level.Level; import net.minecraft.world.level.block.entity.BlockEntity; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; +import org.bukkit.Bukkit; +import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -24,50 +21,43 @@ import java.io.ByteArrayOutputStream; public class RequestBlockEntityPacketListener implements AxiomPacketListener { + private static final Class CraftWorld = Reflection.getClass("org.bukkit.craftbukkit.CraftWorld"); + private static final Reflection.Method getHandle = Reflection.getTypedMethod(CraftWorld, ServerLevel.class); + + private static final Reflection.Method of = Reflection.getTypedMethod(BlockPos.class, BlockPos.class, long.class); + private static final Reflection.Method getBlockEntity = Reflection.getTypedMethod(ServerLevel.class, BlockEntity.class, BlockPos.class); + + private static final Reflection.Method saveWithoutMetadata = Reflection.getTypedMethod(BlockEntity.class, CompoundTag.class, 2); + @Override public void onMessage(@NotNull Player bukkitPlayer, @NotNull ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - long id = friendlyByteBuf.readLong(); + long id = buf.readLong(); - if (!bukkitPlayer.hasPermission("axiom.*")) { - // We always send an 'empty' response in order to make the client happy - sendEmptyResponse(bukkitPlayer, id); //TODO - return; - } - - ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); - MinecraftServer server = player.getServer(); - if (server == null) { - sendEmptyResponse(bukkitPlayer, id); - return; - } - - ResourceKey worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); - ServerLevel level = server.getLevel(worldKey); - if (level == null) { + World world = Bukkit.getWorld(MojBuf.readKey(buf)); + if (world == null) { sendEmptyResponse(bukkitPlayer, id); return; } + ServerLevel level = getHandle.invoke(world); Long2ObjectMap map = new Long2ObjectOpenHashMap<>(); ByteArrayOutputStream baos = new ByteArrayOutputStream(); - BlockPos.MutableBlockPos mutableBlockPos = new BlockPos.MutableBlockPos(); // Save and compress block entities - int count = friendlyByteBuf.readVarInt(); + int count = MojBuf.readVarInt(buf); for (int i = 0; i < count; i++) { - long pos = friendlyByteBuf.readLong(); - BlockEntity blockEntity = level.getBlockEntity(mutableBlockPos.set(pos)); + long pos = buf.readLong(); + BlockEntity blockEntity = getBlockEntity.invoke(level, of.invoke(null, pos)); if (blockEntity != null) { - CompoundTag tag = blockEntity.saveWithoutMetadata(); + CompoundTag tag = saveWithoutMetadata.invoke(blockEntity); map.put(pos, CompressedBlockEntity.compress(tag, baos)); } } // Send response packet - FriendlyByteBuf out = new FriendlyByteBuf(Unpooled.buffer(16)); + ByteBuf out = MojBuf.unpooled(); out.writeLong(id); - out.writeVarInt(map.size()); + MojBuf.writeVarInt(buf, map.size()); for (Long2ObjectMap.Entry entry : map.long2ObjectEntrySet()) { out.writeLong(entry.getLongKey()); entry.getValue().write(out); @@ -75,6 +65,11 @@ public class RequestBlockEntityPacketListener implements AxiomPacketListener { OutChannel.BLOCK_ENTITIES.send(bukkitPlayer, out); } + @Override + public void onMissingPerm(Player player, ByteBuf buf) { + sendEmptyResponse(player, buf.readLong()); + } + private void sendEmptyResponse(Player player, long id) { ByteBuf buf = Unpooled.buffer(16); buf.writeLong(id); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index 56379be..89ce25c 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -1,8 +1,10 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.Reflection; import com.moulberry.axiom.buffer.BiomeBuffer; import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.integration.RegionProtection; import io.netty.buffer.ByteBuf; @@ -13,7 +15,6 @@ import net.minecraft.core.Holder; import net.minecraft.core.Registry; import net.minecraft.core.SectionPos; import net.minecraft.core.registries.Registries; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; @@ -35,49 +36,31 @@ import net.minecraft.world.level.lighting.LightEngine; import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R1.CraftServer; import org.bukkit.entity.Player; -import xyz.jpenilla.reflectionremapper.ReflectionRemapper; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.*; public class SetBlockBufferPacketListener implements AxiomPacketListener { - private final Method updateBlockEntityTicker; - - public SetBlockBufferPacketListener() { - ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); - String methodName = reflectionRemapper.remapMethodName(LevelChunk.class, "updateBlockEntityTicker", BlockEntity.class); - - try { - this.updateBlockEntityTicker = LevelChunk.class.getDeclaredMethod(methodName, BlockEntity.class); - this.updateBlockEntityTicker.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - } + private static final Reflection.Method updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class); @Override public void onMessage(Player player, ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); MinecraftServer server = ((CraftServer)player.getServer()).getServer(); if (server == null) return; - ResourceKey worldKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); - friendlyByteBuf.readUUID(); // Discard, we don't need to associate buffers - boolean continuation = friendlyByteBuf.readBoolean(); + ResourceKey worldKey = buf.readResourceKey(Registries.DIMENSION); + MojBuf.readUUID(buf); // Discard, we don't need to associate buffers - if (!continuation) { - friendlyByteBuf.readNbt(); // Discard sourceInfo + if (!buf.readBoolean()) { + MojBuf.readNbt(buf); // Discard sourceInfo } - byte type = friendlyByteBuf.readByte(); + byte type = buf.readByte(); if (type == 0) { - BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf); + BlockBuffer buffer = BlockBuffer.load(buf); applyBlockBuffer(player, server, buffer, worldKey); } else if (type == 1) { - BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf); + BiomeBuffer buffer = BiomeBuffer.load(buf); applyBiomeBuffer(server, buffer, worldKey); } else { throw new RuntimeException("Unknown buffer type: " + type); @@ -186,11 +169,7 @@ public class SetBlockBufferPacketListener implements AxiomPacketListener { // Block entity is here and the type is correct blockEntity.setBlockState(blockState); - try { - this.updateBlockEntityTicker.invoke(chunk, blockEntity); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } + updateBlockEntityTicker.invoke(chunk, blockEntity); } else { // Block entity type isn't correct, we need to recreate it chunk.removeBlockEntity(blockPos); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index 8585f24..34eb4d8 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -1,11 +1,11 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.Reflection; import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.integration.VersionTranslator; import io.netty.buffer.ByteBuf; import net.minecraft.core.BlockPos; import net.minecraft.core.SectionPos; -import net.minecraft.network.FriendlyByteBuf; import net.minecraft.server.level.ServerLevel; import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.level.block.Block; @@ -20,28 +20,12 @@ import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; -import xyz.jpenilla.reflectionremapper.ReflectionRemapper; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.util.Map; public class SetBlockPacketListener implements AxiomPacketListener { - private final Method updateBlockEntityTicker; - - public SetBlockPacketListener() { - ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar(); - String methodName = reflectionRemapper.remapMethodName(LevelChunk.class, "updateBlockEntityTicker", BlockEntity.class); - - try { - this.updateBlockEntityTicker = LevelChunk.class.getDeclaredMethod(methodName, BlockEntity.class); - this.updateBlockEntityTicker.setAccessible(true); - } catch (Exception e) { - e.printStackTrace(); - throw new RuntimeException(e); - } - } + private static final Reflection.Method updateBlockEntityTicker = Reflection.getMethod(LevelChunk.class, BlockEntity.class); @Override public void onMessage(@NotNull Player bukkitPlayer, @NotNull ByteBuf buf) { @@ -51,11 +35,10 @@ public class SetBlockPacketListener implements AxiomPacketListener { if (modifyWorldEvent.isCancelled()) return; // Read packet - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - BlockPos blockPos = friendlyByteBuf.readBlockPos(); - BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(VersionTranslator.impl.blockStateMapper(bukkitPlayer).apply(friendlyByteBuf.readVarInt())); - boolean updateNeighbors = friendlyByteBuf.readBoolean(); - int sequenceId = friendlyByteBuf.readInt(); + BlockPos blockPos = buf.readBlockPos(); + BlockState blockState = Block.BLOCK_STATE_REGISTRY.byId(VersionTranslator.impl.blockStateMapper(bukkitPlayer).apply(buf.readVarInt())); + boolean updateNeighbors = buf.readBoolean(); + int sequenceId = buf.readInt(); ServerPlayer player = ((CraftPlayer)bukkitPlayer).getHandle(); @@ -116,11 +99,7 @@ public class SetBlockPacketListener implements AxiomPacketListener { // Just update the state and ticker and move on blockEntity.setBlockState(blockState); - try { - this.updateBlockEntityTicker.invoke(chunk, blockEntity); - } catch (IllegalAccessException | InvocationTargetException e) { - throw new RuntimeException(e); - } + updateBlockEntityTicker.invoke(chunk, blockEntity); } else { // Block entity type isn't correct, we need to recreate it chunk.removeBlockEntity(blockPos); diff --git a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java index 642fd1d..cb4abca 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java @@ -1,10 +1,10 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.View; import com.moulberry.axiom.persistence.UUIDDataType; import io.netty.buffer.ByteBuf; -import net.minecraft.network.FriendlyByteBuf; import org.bukkit.entity.Player; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; @@ -17,9 +17,8 @@ public class SetEditorViewsPacketListener implements AxiomPacketListener { @Override public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - UUID uuid = friendlyByteBuf.readUUID(); - List views = friendlyByteBuf.readList(View::read); + UUID uuid = MojBuf.readUUID(buf); + List views = MojBuf.readList(buf, View::read); PersistentDataContainer container = player.getPersistentDataContainer(); container.set(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE, uuid); diff --git a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java index b44c283..ab2222f 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java @@ -1,14 +1,20 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.Reflection; import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent; import io.netty.buffer.ByteBuf; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.player.Abilities; import org.bukkit.Bukkit; -import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; public class SetFlySpeedPacketListener implements AxiomPacketListener { + private static final Reflection.Method getAbilities = Reflection.getTypedMethod(ServerPlayer.class, Abilities.class); + private static final Reflection.Field flyingSpeed = Reflection.getField(Abilities.class, float.class); + @Override public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { float flySpeed = buf.readFloat(); @@ -19,7 +25,7 @@ public class SetFlySpeedPacketListener implements AxiomPacketListener { if (flySpeedChangeEvent.isCancelled()) return; // Change flying speed - ((CraftPlayer)player).getHandle().getAbilities().setFlyingSpeed(flySpeed); + flyingSpeed.set(getAbilities.invoke(AxiomPaper.convert(player)), flySpeed); } } diff --git a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java index b9c1f0f..29943f2 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java @@ -1,12 +1,11 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.persistence.ItemStackDataType; import io.netty.buffer.ByteBuf; -import net.minecraft.network.FriendlyByteBuf; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; @@ -15,15 +14,14 @@ public class SetHotbarSlotPacketListener implements AxiomPacketListener { @Override public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - int index = friendlyByteBuf.readByte(); + int index = buf.readByte(); if (index < 0 || index >= 9*9) return; - net.minecraft.world.item.ItemStack nmsStack = friendlyByteBuf.readItem(); + ItemStack stack = MojBuf.readItem(buf); PersistentDataContainer container = player.getPersistentDataContainer(); PersistentDataContainer hotbarItems = container.get(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER); if (hotbarItems == null) hotbarItems = container.getAdapterContext().newPersistentDataContainer(); - hotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, CraftItemStack.asCraftMirror(nmsStack)); + hotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, stack); container.set(AxiomConstants.HOTBAR_DATA, PersistentDataType.TAG_CONTAINER, hotbarItems); } diff --git a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java index 6006180..0195c50 100644 --- a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java @@ -1,13 +1,11 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.persistence.ItemStackDataType; import io.netty.buffer.ByteBuf; -import net.minecraft.network.FriendlyByteBuf; import org.bukkit.GameMode; import org.bukkit.Material; -import org.bukkit.NamespacedKey; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataContainer; @@ -18,13 +16,12 @@ public class SwitchActiveHotbarPacketListener implements AxiomPacketListener { @Override public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - int oldHotbarIndex = friendlyByteBuf.readByte(); - int activeHotbarIndex = friendlyByteBuf.readByte(); + int oldHotbarIndex = buf.readByte(); + int activeHotbarIndex = buf.readByte(); ItemStack[] hotbarItems = new ItemStack[9]; for (int i=0; i<9; i++) { - hotbarItems[i] = CraftItemStack.asCraftMirror(friendlyByteBuf.readItem()); + hotbarItems[i] = MojBuf.readItem(buf); } PersistentDataContainer container = player.getPersistentDataContainer(); @@ -40,10 +37,10 @@ public class SwitchActiveHotbarPacketListener implements AxiomPacketListener { } else { stack = stack.clone(); } - containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, stack); + containerHotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, stack); } int index = activeHotbarIndex*9 + i; - containerHotbarItems.set(new NamespacedKey("axiom", "slot_"+index), ItemStackDataType.INSTANCE, hotbarItems[i].clone()); + containerHotbarItems.set(AxiomConstants.axiomKey("slot_"+index), ItemStackDataType.INSTANCE, hotbarItems[i].clone()); if (player.getGameMode() == GameMode.CREATIVE) player.getInventory().setItem(i, hotbarItems[i]); } diff --git a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java index 9bd60eb..ae76cdd 100644 --- a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java @@ -1,14 +1,10 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.buffer.MojBuf; import com.moulberry.axiom.event.AxiomTeleportEvent; -import io.netty.buffer.ByteBuf; -import net.minecraft.core.registries.Registries; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.world.level.Level; import org.bukkit.Bukkit; import org.bukkit.Location; -import org.bukkit.NamespacedKey; import org.bukkit.World; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; @@ -16,17 +12,14 @@ import org.jetbrains.annotations.NotNull; public class TeleportPacketListener implements AxiomPacketListener { @Override - public void onMessage(@NotNull Player player, @NotNull ByteBuf buf) { - FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(buf); - ResourceKey resourceKey = friendlyByteBuf.readResourceKey(Registries.DIMENSION); - double x = friendlyByteBuf.readDouble(); - double y = friendlyByteBuf.readDouble(); - double z = friendlyByteBuf.readDouble(); - float yRot = friendlyByteBuf.readFloat(); - float xRot = friendlyByteBuf.readFloat(); + public void onMessage(@NotNull Player player, @NotNull FriendlyByteBuf buf) { + World world = Bukkit.getWorld(MojBuf.readKey(buf)); + double x = buf.readDouble(); + double y = buf.readDouble(); + double z = buf.readDouble(); + float yRot = buf.readFloat(); + float xRot = buf.readFloat(); - NamespacedKey namespacedKey = new NamespacedKey(resourceKey.location().getNamespace(), resourceKey.location().getPath()); - World world = Bukkit.getWorld(namespacedKey); if (world == null) return; // Call event diff --git a/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java b/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java index bf37e0f..96d907f 100644 --- a/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java +++ b/src/main/java/com/moulberry/axiom/persistence/ItemStackDataType.java @@ -1,8 +1,8 @@ package com.moulberry.axiom.persistence; +import com.moulberry.axiom.Reflection; +import com.moulberry.axiom.buffer.MojBuf; import net.minecraft.nbt.CompoundTag; -import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack; -import org.bukkit.craftbukkit.v1_20_R1.persistence.CraftPersistentDataContainer; import org.bukkit.inventory.ItemStack; import org.bukkit.persistence.PersistentDataAdapterContext; import org.bukkit.persistence.PersistentDataContainer; @@ -10,9 +10,9 @@ import org.bukkit.persistence.PersistentDataType; import org.jetbrains.annotations.NotNull; public class ItemStackDataType implements PersistentDataType { - public static ItemStackDataType INSTANCE = new ItemStackDataType(); - private ItemStackDataType() { - } + private ItemStackDataType() {} + + public static final ItemStackDataType INSTANCE = new ItemStackDataType(); @Override public @NotNull Class getPrimitiveType() { @@ -24,23 +24,23 @@ public class ItemStackDataType implements PersistentDataType CraftPersistentDataContainer = Reflection.getClass("org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer"); + private static final Reflection.Method putAll = Reflection.getMethod(CraftPersistentDataContainer, "putAll", CompoundTag.class); + private static final Reflection.Method toTagCompound = Reflection.getTypedMethod(CraftPersistentDataContainer, "toTagCompound", CompoundTag.class); + + private static final Reflection.Method save = Reflection.getTypedMethod(net.minecraft.world.item.ItemStack.class, CompoundTag.class, CompoundTag.class); @Override - public @NotNull PersistentDataContainer toPrimitive(@NotNull ItemStack complex, @NotNull PersistentDataAdapterContext context) { - net.minecraft.world.item.ItemStack nmsStack = CraftItemStack.asNMSCopy(complex); - if (nmsStack == null) nmsStack = net.minecraft.world.item.ItemStack.EMPTY; - CompoundTag tag = new CompoundTag(); - nmsStack.save(tag); + public @NotNull PersistentDataContainer toPrimitive(@NotNull ItemStack stack, @NotNull PersistentDataAdapterContext context) { + CompoundTag tag = save.invoke(MojBuf.toMojang(stack), new CompoundTag()); PersistentDataContainer container = context.newPersistentDataContainer(); - ((CraftPersistentDataContainer)container).putAll(tag); + putAll.invoke(container, tag); return container; } + private static final Reflection.Method of = Reflection.getTypedMethod(net.minecraft.world.item.ItemStack.class, net.minecraft.world.item.ItemStack.class, CompoundTag.class); @Override public @NotNull ItemStack fromPrimitive(@NotNull PersistentDataContainer primitive, @NotNull PersistentDataAdapterContext context) { - CompoundTag tag = ((CraftPersistentDataContainer)primitive).toTagCompound(); - net.minecraft.world.item.ItemStack nmsStack = net.minecraft.world.item.ItemStack.of(tag); - - return CraftItemStack.asCraftMirror(nmsStack); + return MojBuf.toBukkit(of.invoke(null, toTagCompound.invoke(primitive))); } } diff --git a/src/main/java/com/moulberry/axiom/persistence/UUIDDataType.java b/src/main/java/com/moulberry/axiom/persistence/UUIDDataType.java index 12fca84..a69322c 100644 --- a/src/main/java/com/moulberry/axiom/persistence/UUIDDataType.java +++ b/src/main/java/com/moulberry/axiom/persistence/UUIDDataType.java @@ -7,9 +7,9 @@ import java.nio.ByteBuffer; import java.util.UUID; public class UUIDDataType implements PersistentDataType { - public static UUIDDataType INSTANCE = new UUIDDataType(); - private UUIDDataType() { - } + private UUIDDataType() {} + + public static final UUIDDataType INSTANCE = new UUIDDataType(); public Class getPrimitiveType() { return byte[].class;