From da65c9d3c1d475a3f0061ed6209a01966a2e3b5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maurice=20Eisenbl=C3=A4tter?= Date: Wed, 26 Jun 2024 04:07:16 +0200 Subject: [PATCH] Improve/cleanup injector (part 2) (#3042) * fix(#3024): cache noop accessor on ctor lookup failure * fix(#3023): add client intent enum wrapper --- .../concurrency/AbstractIntervalTree.java | 494 ------------------ .../protocol/concurrency/BlockingHashMap.java | 266 ---------- .../protocol/events/AbstractStructure.java | 83 ++- ...tenerInvoker.java => ListenerManager.java} | 18 +- .../injector/PacketFilterManager.java | 27 +- .../injector/netty/ChannelListener.java | 59 --- .../protocol/injector/netty/Injector.java | 12 +- .../injector/netty/channel/EmptyInjector.java | 12 +- .../netty/channel/InjectionFactory.java | 12 +- .../netty/channel/NettyChannelInjector.java | 144 ++--- .../netty/channel/PacketListenerInvoker.java | 237 +++++++++ .../InjectionChannelInboundHandler.java | 19 +- .../netty/manager/NetworkManagerInjector.java | 81 +-- .../temporary/TemporaryPlayerFactory.java | 2 +- .../accessors/ConstructorAccessor.java | 15 + .../reflect/instances/MinecraftGenerator.java | 26 +- .../protocol/utility/MinecraftReflection.java | 2 +- .../protocol/wrappers/EnumWrappers.java | 36 +- .../concurrency/BlockingHashMapTest.java | 53 -- .../channel/PacketListenerInvokerTest.java | 21 + .../instances/MinecraftGeneratorTest.java | 36 ++ 21 files changed, 519 insertions(+), 1136 deletions(-) delete mode 100644 src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java delete mode 100644 src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java rename src/main/java/com/comphenix/protocol/injector/{ListenerInvoker.java => ListenerManager.java} (75%) delete mode 100644 src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java create mode 100644 src/main/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvoker.java delete mode 100644 src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java create mode 100644 src/test/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvokerTest.java create mode 100644 src/test/java/com/comphenix/protocol/reflect/instances/MinecraftGeneratorTest.java diff --git a/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java b/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java deleted file mode 100644 index 643fbb158..000000000 --- a/src/main/java/com/comphenix/protocol/concurrency/AbstractIntervalTree.java +++ /dev/null @@ -1,494 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.concurrency; - -import com.google.common.base.Objects; -import com.google.common.collect.Range; - -import java.util.HashSet; -import java.util.Map; -import java.util.NavigableMap; -import java.util.Set; -import java.util.TreeMap; - -/** - * Represents a generic store of intervals and associated values. No two intervals - * can overlap in this representation. - *

- * Note that this implementation is not thread safe. - * - * @author Kristian - * - * @param - type of the key. Must implement Comparable. - * @param - type of the value to associate. - */ -public abstract class AbstractIntervalTree, TValue> { - - protected enum State { - OPEN, - CLOSE, - BOTH - } - - /** - * Represents a range and a value in this interval tree. - */ - public class Entry implements Map.Entry, TValue> { - private final EndPoint left; - private final EndPoint right; - - Entry(EndPoint left, EndPoint right) { - if (left == null) - throw new IllegalAccessError("left cannot be NUll"); - if (right == null) - throw new IllegalAccessError("right cannot be NUll"); - if (left.key.compareTo(right.key) > 0) - throw new IllegalArgumentException( - "Left key (" + left.key + ") cannot be greater than the right key (" + right.key + ")"); - - this.left = left; - this.right = right; - } - - @Override - public Range getKey() { - return Range.closed(left.key, right.key); - } - - @Override - public TValue getValue() { - return left.value; - } - - @Override - public TValue setValue(TValue value) { - TValue old = left.value; - - // Set both end points - left.value = value; - right.value = value; - return old; - } - - @SuppressWarnings("rawtypes") - @Override - public boolean equals(Object obj) { - // Quick equality check - if (obj == this) { - return true; - } else if (obj instanceof AbstractIntervalTree.Entry) { - return Objects.equal(left.key, ((AbstractIntervalTree.Entry) obj).left.key) && - Objects.equal(right.key, ((AbstractIntervalTree.Entry) obj).right.key) && - Objects.equal(left.value, ((AbstractIntervalTree.Entry) obj).left.value); - } else { - return false; - } - } - - @Override - public int hashCode() { - return Objects.hashCode(left.key, right.key, left.value); - } - - @Override - public String toString() { - return String.format("Value %s at [%s, %s]", left.value, left.key, right.key); - } - } - - /** - * Represents a single end point (open, close or both) of a range. - */ - protected class EndPoint { - - // Whether or not the end-point is opening a range, closing a range or both. - public State state; - - // The value this range contains - public TValue value; - - // The key of this end point - public TKey key; - - public EndPoint(State state, TKey key, TValue value) { - this.state = state; - this.key = key; - this.value = value; - } - } - - // To quickly look up ranges we'll index them by endpoints - protected NavigableMap bounds = new TreeMap<>(); - - /** - * Removes every interval that intersects with the given range. - * @param lowerBound - lowest value to remove. - * @param upperBound - highest value to remove. - * @return Intervals that were removed - */ - public Set remove(TKey lowerBound, TKey upperBound) { - return remove(lowerBound, upperBound, false); - } - - /** - * Removes every interval that intersects with the given range. - * @param lowerBound - lowest value to remove. - * @param upperBound - highest value to remove. - * @param preserveDifference - whether or not to preserve the intervals that are partially outside. - * @return Intervals that were removed - */ - public Set remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) { - checkBounds(lowerBound, upperBound); - NavigableMap range = bounds.subMap(lowerBound, true, upperBound, true); - - EndPoint first = getNextEndPoint(lowerBound, true); - EndPoint last = getPreviousEndPoint(upperBound, true); - - // Used while resizing intervals - EndPoint previous = null; - EndPoint next = null; - - Set resized = new HashSet<>(); - Set removed = new HashSet<>(); - - // Remove the previous element too. A close end-point must be preceded by an OPEN end-point. - if (first != null && first.state == State.CLOSE) { - previous = getPreviousEndPoint(first.key, false); - - // Add the interval back - if (previous != null) { - removed.add(getEntry(previous, first)); - } - } - - // Get the closing element too. - if (last != null && last.state == State.OPEN) { - next = getNextEndPoint(last.key, false); - - if (next != null) { - removed.add(getEntry(last, next)); - } - } - - // Now remove both ranges - removeEntrySafely(previous, first); - removeEntrySafely(last, next); - - // Add new resized intervals - if (preserveDifference) { - if (previous != null) { - resized.add(putUnsafe(previous.key, decrementKey(lowerBound), previous.value)); - } - if (next != null) { - resized.add(putUnsafe(incrementKey(upperBound), next.key, next.value)); - } - } - - // Get the removed entries too - getEntries(removed, range); - invokeEntryRemoved(removed); - - if (preserveDifference) { - invokeEntryAdded(resized); - } - - // Remove the range as well - range.clear(); - return removed; - } - - /** - * Retrieve the entry from a given set of end points. - * @param left - leftmost end point. - * @param right - rightmost end point. - * @return The associated entry. - */ - protected Entry getEntry(EndPoint left, EndPoint right) { - if (left == null) - throw new IllegalArgumentException("left endpoint cannot be NULL."); - if (right == null) - throw new IllegalArgumentException("right endpoint cannot be NULL."); - - // Make sure the order is correct - if (right.key.compareTo(left.key) < 0) { - return getEntry(right, left); - } else { - return new Entry(left, right); - } - } - - private void removeEntrySafely(EndPoint left, EndPoint right) { - if (left != null && right != null) { - bounds.remove(left.key); - bounds.remove(right.key); - } - } - - // Adds a given end point - protected EndPoint addEndPoint(TKey key, TValue value, State state) { - EndPoint endPoint = bounds.get(key); - - if (endPoint != null) { - endPoint.state = State.BOTH; - } else { - endPoint = new EndPoint(state, key, value); - bounds.put(key, endPoint); - } - return endPoint; - } - - /** - * Associates a given interval of keys with a certain value. Any previous - * association will be overwritten in the given interval. - *

- * Overlapping intervals are not permitted. A key can only be associated with a single value. - * - * @param lowerBound - the minimum key (inclusive). - * @param upperBound - the maximum key (inclusive). - * @param value - the value, or NULL to reset this range. - */ - public void put(TKey lowerBound, TKey upperBound, TValue value) { - // While we don't permit overlapping intervals, we'll still allow overwriting existing intervals. - remove(lowerBound, upperBound, true); - invokeEntryAdded(putUnsafe(lowerBound, upperBound, value)); - } - - /** - * Associates a given interval without performing any interval checks. - * @param lowerBound - the minimum key (inclusive). - * @param upperBound - the maximum key (inclusive). - * @param value - the value, or NULL to reset the range. - */ - private Entry putUnsafe(TKey lowerBound, TKey upperBound, TValue value) { - // OK. Add the end points now - if (value != null) { - EndPoint left = addEndPoint(lowerBound, value, State.OPEN); - EndPoint right = addEndPoint(upperBound, value, State.CLOSE); - - return new Entry(left, right); - } else { - return null; - } - } - - /** - * Used to verify the validity of the given interval. - * @param lowerBound - lower bound (inclusive). - * @param upperBound - upper bound (inclusive). - */ - private void checkBounds(TKey lowerBound, TKey upperBound) { - if (lowerBound == null) - throw new IllegalAccessError("lowerbound cannot be NULL."); - if (upperBound == null) - throw new IllegalAccessError("upperBound cannot be NULL."); - if (upperBound.compareTo(lowerBound) < 0) - throw new IllegalArgumentException("upperBound cannot be less than lowerBound."); - } - - /** - * Determines if the given key is within an interval. - * @param key - key to check. - * @return TRUE if the given key is within an interval in this tree, FALSE otherwise. - */ - public boolean containsKey(TKey key) { - return getEndPoint(key) != null; - } - - /** - * Enumerates over every range in this interval tree. - * @return Number of ranges. - */ - public Set entrySet() { - // Don't mind the Java noise - Set result = new HashSet<>(); - getEntries(result, bounds); - return result; - } - - /** - * Remove every interval. - */ - public void clear() { - if (!bounds.isEmpty()) { - remove(bounds.firstKey(), bounds.lastKey()); - } - } - - /** - * Converts a map of end points into a set of entries. - * @param destination - set of entries. - * @param map - a map of end points. - */ - private void getEntries(Set destination, NavigableMap map) { - Map.Entry last = null; - - for (Map.Entry entry : map.entrySet()) { - switch (entry.getValue().state) { - case BOTH: - EndPoint point = entry.getValue(); - destination.add(new Entry(point, point)); - break; - case CLOSE: - if (last != null) { - destination.add(new Entry(last.getValue(), entry.getValue())); - } - break; - case OPEN: - // We don't know the full range yet - last = entry; - break; - default: - throw new IllegalStateException("Illegal open/close state detected."); - } - } - } - - /** - * Inserts every range from the given tree into the current tree. - * @param other - the other tree to read from. - */ - public void putAll(AbstractIntervalTree other) { - // Naively copy every range. - for (Entry entry : other.entrySet()) { - put(entry.left.key, entry.right.key, entry.getValue()); - } - } - - /** - * Retrieves the value of the range that matches the given key, or NULL if nothing was found. - * @param key - the level to read for. - * @return The correct amount of experience, or NULL if nothing was recorded. - */ - public TValue get(TKey key) { - EndPoint point = getEndPoint(key); - - if (point != null) - return point.value; - else - return null; - } - - /** - * Get the left-most end-point associated with this key. - * @param key - key to search for. - * @return The end point found, or NULL. - */ - protected EndPoint getEndPoint(TKey key) { - EndPoint ends = bounds.get(key); - - if (ends != null) { - // Always return the end point to the left - if (ends.state == State.CLOSE) { - Map.Entry left = bounds.floorEntry(decrementKey(key)); - return left != null ? left.getValue() : null; - - } else { - return ends; - } - - } else { - // We need to determine if the point intersects with a range - Map.Entry left = bounds.floorEntry(key); - - // We only need to check to the left - if (left != null && left.getValue().state == State.OPEN) { - return left.getValue(); - } else { - return null; - } - } - } - - /** - * Get the previous end point of a given key. - * @param point - the point to search with. - * @param inclusive - whether or not to include the current point in the search. - * @return The previous end point of a given given key, or NULL if not found. - */ - protected EndPoint getPreviousEndPoint(TKey point, boolean inclusive) { - if (point != null) { - Map.Entry previous = bounds.floorEntry(inclusive ? point : decrementKey(point)); - - if (previous != null) - return previous.getValue(); - } - return null; - } - - /** - * Get the next end point of a given key. - * @param point - the point to search with. - * @param inclusive - whether or not to include the current point in the search. - * @return The next end point of a given given key, or NULL if not found. - */ - protected EndPoint getNextEndPoint(TKey point, boolean inclusive) { - if (point != null) { - Map.Entry next = bounds.ceilingEntry(inclusive ? point : incrementKey(point)); - - if (next != null) - return next.getValue(); - } - return null; - } - - private void invokeEntryAdded(Entry added) { - if (added != null) { - onEntryAdded(added); - } - } - - private void invokeEntryAdded(Set added) { - for (Entry entry : added) { - onEntryAdded(entry); - } - } - - private void invokeEntryRemoved(Set removed) { - for (Entry entry : removed) { - onEntryRemoved(entry); - } - } - - // Listeners for added or removed entries - /** - * Invoked when an entry is added. - * @param added - the entry that was added. - */ - protected void onEntryAdded(Entry added) { } - - /** - * Invoked when an entry is removed. - * @param removed - the removed entry. - */ - protected void onEntryRemoved(Entry removed) { } - - // Helpers for decrementing or incrementing key values - /** - * Decrement the given key by one unit. - * @param key - the key that should be decremented. - * @return The new decremented key. - */ - protected abstract TKey decrementKey(TKey key); - - /** - * Increment the given key by one unit. - * @param key - the key that should be incremented. - * @return The new incremented key. - */ - protected abstract TKey incrementKey(TKey key); -} diff --git a/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java b/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java deleted file mode 100644 index 725db00f4..000000000 --- a/src/main/java/com/comphenix/protocol/concurrency/BlockingHashMap.java +++ /dev/null @@ -1,266 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.concurrency; - -import com.comphenix.protocol.utility.SafeCacheBuilder; -import com.google.common.cache.CacheLoader; -import com.google.common.cache.RemovalCause; -import com.google.common.cache.RemovalListener; -import com.google.common.cache.RemovalNotification; - -import java.util.Collection; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.TimeUnit; - -/** - * A map that supports blocking on read operations. Null keys are not supported. - *

- * Values are stored as weak references, and will be automatically removed once they've all been dereferenced. - *

- * @author Kristian - * - * @param - type of the key. - * @param - type of the value. - */ -public class BlockingHashMap { - // Map of values - private final ConcurrentMap backingMap; - - // Map of locked objects - private final ConcurrentMap locks; - - /** - * Retrieve a cache loader that will always throw an exception. - * @param Type of the key - * @param Type of the value - * @return An invalid cache loader. - */ - public static CacheLoader newInvalidCacheLoader() { - return new CacheLoader() { - @Override - public TValue load(TKey key) throws Exception { - throw new IllegalStateException("Illegal use. Access the map directly instead."); - } - }; - } - - /** - * Initialize a new map. - */ - public BlockingHashMap() { - backingMap = SafeCacheBuilder.newBuilder(). - weakValues(). - removalListener( - new RemovalListener() { - @Override - public void onRemoval(RemovalNotification entry) { - // Clean up locks too - if (entry.getCause() != RemovalCause.REPLACED) { - locks.remove(entry.getKey()); - } - } - }). - build(BlockingHashMap. newInvalidCacheLoader()); - - // Normal concurrent hash map - locks = new ConcurrentHashMap<>(); - } - - /** - * Initialize a new map. - * @param Type of the key - * @param Type of the value - * @return The created map. - */ - public static BlockingHashMap create() { - return new BlockingHashMap<>(); - } - - /** - * Waits until a value has been associated with the given key, and then retrieves that value. - * @param key - the key whose associated value is to be returned - * @return The value to which the specified key is mapped. - * @throws InterruptedException If the current thread got interrupted while waiting. - */ - public TValue get(TKey key) throws InterruptedException { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - - TValue value = backingMap.get(key); - - // Only lock if no value is available - if (value == null) { - final Object lock = getLock(key); - - synchronized (lock) { - while (value == null) { - lock.wait(); - value = backingMap.get(key); - } - } - } - - return value; - } - - /** - * Waits until a value has been associated with the given key, and then retrieves that value. - * @param key - the key whose associated value is to be returned - * @param timeout - the amount of time to wait until an association has been made. - * @param unit - unit of timeout. - * @return The value to which the specified key is mapped, or NULL if the timeout elapsed. - * @throws InterruptedException If the current thread got interrupted while waiting. - */ - public TValue get(TKey key, long timeout, TimeUnit unit) throws InterruptedException { - return get(key, timeout, unit, false); - } - - /** - * Waits until a value has been associated with the given key, and then retrieves that value. - *

- * If timeout is zero, this method will return immediately if it can't find an socket injector. - * - * @param key - the key whose associated value is to be returned - * @param timeout - the amount of time to wait until an association has been made. - * @param unit - unit of timeout. - * @param ignoreInterrupted - TRUE if we should ignore the thread being interrupted, FALSE otherwise. - * @return The value to which the specified key is mapped, or NULL if the timeout elapsed. - * @throws InterruptedException If the current thread got interrupted while waiting. - */ - public TValue get(TKey key, long timeout, TimeUnit unit, boolean ignoreInterrupted) throws InterruptedException { - if (key == null) - throw new IllegalArgumentException("key cannot be NULL."); - if (unit == null) - throw new IllegalArgumentException("Unit cannot be NULL."); - if (timeout < 0) - throw new IllegalArgumentException("Timeout cannot be less than zero."); - - TValue value = backingMap.get(key); - - // Only lock if no value is available - if (value == null && timeout > 0) { - final Object lock = getLock(key); - final long stopTimeNS = System.nanoTime() + unit.toNanos(timeout); - - // Don't exceed the timeout - synchronized (lock) { - while (value == null) { - try { - long remainingTime = stopTimeNS - System.nanoTime(); - - if (remainingTime > 0) { - TimeUnit.NANOSECONDS.timedWait(lock, remainingTime); - value = backingMap.get(key); - } else { - // Timeout elapsed - break; - } - } catch (InterruptedException e) { - // This is fairly dangerous - but we might HAVE to block the thread - if (!ignoreInterrupted) - throw e; - } - } - } - } - return value; - } - - /** - * Associate a given key with the given value. - *

- * Wakes up any blocking getters on this specific key. - * - * @param key - the key to associate. - * @param value - the value. - * @return The previously associated value. - */ - public TValue put(TKey key, TValue value) { - if (value == null) - throw new IllegalArgumentException("This map doesn't support NULL values."); - - final TValue previous = backingMap.put(key, value); - final Object lock = getLock(key); - - // Inform our readers about this change - synchronized (lock) { - lock.notifyAll(); - return previous; - } - } - - /** - * If and only if a key is not present in the map will it be associated with the given value. - * @param key - the key to associate. - * @param value - the value to associate. - * @return The previous value this key has been associated with. - */ - public TValue putIfAbsent(TKey key, TValue value) { - if (value == null) - throw new IllegalArgumentException("This map doesn't support NULL values."); - - final TValue previous = backingMap.putIfAbsent(key, value); - - // No need to unlock readers if we haven't changed anything - if (previous == null) { - final Object lock = getLock(key); - - synchronized (lock) { - lock.notifyAll(); - } - } - return previous; - } - - public int size() { - return backingMap.size(); - } - - public Collection values() { - return backingMap.values(); - } - - public Set keys() { - return backingMap.keySet(); - } - - /** - * Atomically retrieve the lock associated with a given key. - * @param key - the current key. - * @return An asssociated lock. - */ - private Object getLock(TKey key) { - Object lock = locks.get(key); - - if (lock == null) { - Object created = new Object(); - - // Do this atomically - lock = locks.putIfAbsent(key, created); - - // If we succeeded, use the latch we created - otherwise, use the already inserted latch - if (lock == null) { - lock = created; - } - } - - return lock; - } -} diff --git a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java index b915ad38f..47c87caf2 100644 --- a/src/main/java/com/comphenix/protocol/events/AbstractStructure.java +++ b/src/main/java/com/comphenix/protocol/events/AbstractStructure.java @@ -1,17 +1,18 @@ package com.comphenix.protocol.events; -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.reflect.EquivalentConverter; -import com.comphenix.protocol.reflect.StructureModifier; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.utility.StreamSerializer; -import com.comphenix.protocol.wrappers.*; -import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; -import com.comphenix.protocol.wrappers.nbt.NbtBase; -import com.comphenix.protocol.wrappers.nbt.NbtCompound; -import com.comphenix.protocol.wrappers.nbt.NbtFactory; -import com.google.common.base.Preconditions; +import java.lang.reflect.Array; +import java.time.Instant; +import java.util.ArrayList; +import java.util.Collection; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; + +import javax.annotation.Nonnull; + import org.apache.commons.lang.Validate; import org.bukkit.Material; import org.bukkit.Sound; @@ -25,10 +26,49 @@ import org.bukkit.util.Vector; import org.jetbrains.annotations.NotNull; -import javax.annotation.Nonnull; -import java.lang.reflect.Array; -import java.time.Instant; -import java.util.*; +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.reflect.EquivalentConverter; +import com.comphenix.protocol.reflect.StructureModifier; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.utility.MinecraftVersion; +import com.comphenix.protocol.utility.StreamSerializer; +import com.comphenix.protocol.wrappers.BlockPosition; +import com.comphenix.protocol.wrappers.BukkitConverters; +import com.comphenix.protocol.wrappers.ChunkCoordIntPair; +import com.comphenix.protocol.wrappers.Converters; +import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper; +import com.comphenix.protocol.wrappers.Either; +import com.comphenix.protocol.wrappers.EnumWrappers; +import com.comphenix.protocol.wrappers.MinecraftKey; +import com.comphenix.protocol.wrappers.MovingObjectPositionBlock; +import com.comphenix.protocol.wrappers.MultiBlockChangeInfo; +import com.comphenix.protocol.wrappers.Pair; +import com.comphenix.protocol.wrappers.PlayerInfoData; +import com.comphenix.protocol.wrappers.WrappedAttribute; +import com.comphenix.protocol.wrappers.WrappedBlockData; +import com.comphenix.protocol.wrappers.WrappedChatComponent; +import com.comphenix.protocol.wrappers.WrappedDataValue; +import com.comphenix.protocol.wrappers.WrappedDataWatcher; +import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction; +import com.comphenix.protocol.wrappers.WrappedGameProfile; +import com.comphenix.protocol.wrappers.WrappedLevelChunkData; +import com.comphenix.protocol.wrappers.WrappedMessageSignature; +import com.comphenix.protocol.wrappers.WrappedNumberFormat; +import com.comphenix.protocol.wrappers.WrappedParticle; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey; +import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData; +import com.comphenix.protocol.wrappers.WrappedRegistrable; +import com.comphenix.protocol.wrappers.WrappedRegistry; +import com.comphenix.protocol.wrappers.WrappedRemoteChatSessionData; +import com.comphenix.protocol.wrappers.WrappedSaltedSignature; +import com.comphenix.protocol.wrappers.WrappedServerPing; +import com.comphenix.protocol.wrappers.WrappedStatistic; +import com.comphenix.protocol.wrappers.WrappedTeamParameters; +import com.comphenix.protocol.wrappers.WrappedWatchableObject; +import com.comphenix.protocol.wrappers.nbt.NbtBase; +import com.comphenix.protocol.wrappers.nbt.NbtCompound; +import com.comphenix.protocol.wrappers.nbt.NbtFactory; +import com.google.common.base.Preconditions; public abstract class AbstractStructure { protected transient Object handle; @@ -1134,6 +1174,17 @@ public StructureModifier getMessageSignatures() { ); } + /** + * Retrieve a read/write structure for the ClientIntent enum used in Handshake/SetProtocol packet + * @return A modifier for Protocol enum fields. + */ + public StructureModifier getClientIntents() { + // Convert to and from the wrapper + return structureModifier.withType( + EnumWrappers.getClientIntentClass(), + EnumWrappers.getClientIntentConverter()); + } + /** * @param leftConverter converter for left values * @param rightConverter converter for right values diff --git a/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java b/src/main/java/com/comphenix/protocol/injector/ListenerManager.java similarity index 75% rename from src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java rename to src/main/java/com/comphenix/protocol/injector/ListenerManager.java index 2069a5f18..eaed3e699 100644 --- a/src/main/java/com/comphenix/protocol/injector/ListenerInvoker.java +++ b/src/main/java/com/comphenix/protocol/injector/ListenerManager.java @@ -25,7 +25,7 @@ * * @author Kristian */ -public interface ListenerInvoker { +public interface ListenerManager { boolean hasInboundListener(PacketType packetType); @@ -38,24 +38,12 @@ public interface ListenerInvoker { * * @param event - the packet event to invoke. */ - void invokePacketReceiving(PacketEvent event); + void invokeInboundPacketListeners(PacketEvent event); /** * Invokes the given packet event for every registered listener. * * @param event - the packet event to invoke. */ - void invokePacketSending(PacketEvent event); - - /** - * Retrieve the associated type of a packet. - * - * @param packet - the packet. - * @return The packet type. - * @deprecated use - * {@link com.comphenix.protocol.injector.packet.PacketRegistry#getPacketType(PacketType.Protocol, Class)} - * instead. - */ - @Deprecated - PacketType getPacketType(Object packet); + void invokeOutboundPacketListeners(PacketEvent event); } diff --git a/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java b/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java index 3273c7260..d8048aebf 100644 --- a/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java +++ b/src/main/java/com/comphenix/protocol/injector/PacketFilterManager.java @@ -46,11 +46,10 @@ import com.comphenix.protocol.injector.netty.WirePacket; import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector; import com.comphenix.protocol.injector.packet.PacketRegistry; -import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; import com.google.common.collect.ImmutableSet; -public class PacketFilterManager implements ListenerInvoker, InternalManager { +public class PacketFilterManager implements ListenerManager, InternalManager { // plugin verifier reports private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s"); @@ -173,7 +172,7 @@ public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMar } // process outbound - this.networkManagerInjector.getInjector(receiver).sendServerPacket(packet.getHandle(), marker, filters); + this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters); } } @@ -214,7 +213,7 @@ public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMa if (filters) { // post to all listeners PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender); - this.invokePacketReceiving(event); + this.invokeInboundPacketListeners(event); if (event.isCancelled()) { return; } @@ -227,7 +226,7 @@ public void receiveClientPacket(Player sender, PacketContainer packet, NetworkMa } // post to the player inject, reset our cancel state change - this.networkManagerInjector.getInjector(sender).receiveClientPacket(nmsPacket); + this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket); } } @@ -505,33 +504,19 @@ public boolean hasMainThreadListener(PacketType packetType) { } @Override - public void invokePacketReceiving(PacketEvent event) { + public void invokeInboundPacketListeners(PacketEvent event) { if (!this.closed) { this.postPacketToListeners(this.inboundListeners, event, false); } } @Override - public void invokePacketSending(PacketEvent event) { + public void invokeOutboundPacketListeners(PacketEvent event) { if (!this.closed) { this.postPacketToListeners(this.outboundListeners, event, true); } } - @Override - public PacketType getPacketType(Object packet) { - if (!MinecraftReflection.isPacketClass(packet)) { - throw new IllegalArgumentException("Given packet is not a minecraft packet instance"); - } - - PacketType type = PacketRegistry.getPacketType(packet.getClass()); - if (type != null) { - return type; - } - - throw new IllegalArgumentException("Unable to associate given packet " + packet + " with a registered packet!"); - } - private void postPacketToListeners(PacketListenerSet listeners, PacketEvent event, boolean outbound) { try { // append async marker if any async listener for the packet was registered diff --git a/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java b/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java deleted file mode 100644 index 4b2f30872..000000000 --- a/src/main/java/com/comphenix/protocol/injector/netty/ChannelListener.java +++ /dev/null @@ -1,59 +0,0 @@ -package com.comphenix.protocol.injector.netty; - -import com.comphenix.protocol.PacketType; -import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.events.NetworkMarker; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; - -/** - * Represents a listener for received or sent packets. - * - * @author Kristian - */ -public interface ChannelListener { - - /** - * Invoked when a packet is being sent to the client. - *

- * This is invoked on the main thread. - * - * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the network marker. - * @return The packet even that was passed to the listeners, with a possible packet change, or NULL. - */ - PacketEvent onPacketSending(Injector injector, PacketContainer packet, NetworkMarker marker); - - /** - * Invoked when a packet is being received from a client. - *

- * This is invoked on an asynchronous worker thread. - * - * @param injector - the channel injector. - * @param packet - the packet. - * @param marker - the associated network marker, if any. - * @return The packet even that was passed to the listeners, with a possible packet change, or NULL. - */ - PacketEvent onPacketReceiving(Injector injector, PacketContainer packet, NetworkMarker marker); - - boolean hasInboundListener(PacketType packetType); - - boolean hasOutboundListener(PacketType packetType); - - boolean hasMainThreadListener(PacketType type); - - /** - * Retrieve the current error reporter. - * - * @return The error reporter. - */ - ErrorReporter getReporter(); - - /** - * Determine if debug mode is enabled. - * - * @return TRUE if it is, FALSE otherwise. - */ - boolean isDebug(); -} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/Injector.java b/src/main/java/com/comphenix/protocol/injector/netty/Injector.java index 87a586185..ce8eec128 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/Injector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/Injector.java @@ -15,6 +15,8 @@ */ public interface Injector { + SocketAddress getAddress(); + /** * Retrieve the current protocol version of the player. * @@ -41,12 +43,14 @@ public interface Injector { * @param marker - the network marker. * @param filtered - whether or not the packet is filtered. */ - void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered); + void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered); - void receiveClientPacket(Object packet); + void readServerboundPacket(Object packet); void sendWirePacket(WirePacket packet); + void disconnect(String message); + /** * Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different * protocol states. @@ -56,8 +60,6 @@ public interface Injector { */ Protocol getCurrentProtocol(PacketType.Sender sender); - SocketAddress getAddress(); - /** * Retrieve the current player or temporary player associated with the injector. * @@ -72,8 +74,6 @@ public interface Injector { */ void setPlayer(Player player); - void disconnect(String message); - boolean isConnected(); /** diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java index a75440a96..59077d9bf 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/EmptyInjector.java @@ -42,17 +42,21 @@ public void close() { } @Override - public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { + public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) { } @Override - public void receiveClientPacket(Object packet) { + public void readServerboundPacket(Object packet) { } @Override public void sendWirePacket(WirePacket packet) { } + @Override + public void disconnect(String message) { + } + @Override public Protocol getCurrentProtocol(PacketType.Sender sender) { return Protocol.HANDSHAKING; @@ -68,10 +72,6 @@ public void setPlayer(Player player) { this.player = player; } - @Override - public void disconnect(String message) { - } - @Override public boolean isConnected() { return false; diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java index 68ae5a8cd..fa8a07419 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/InjectionFactory.java @@ -23,7 +23,7 @@ import org.bukkit.plugin.Plugin; import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.ListenerManager; import com.comphenix.protocol.injector.netty.Injector; import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -50,16 +50,16 @@ public class InjectionFactory { private final Plugin plugin; // protocol lib stuff - private final ChannelListener channelListener; private final ErrorReporter errorReporter; + private final ListenerManager listenerManager; // state of the factory private boolean closed; - public InjectionFactory(Plugin plugin, ChannelListener channelListener, ErrorReporter errorReporter) { + public InjectionFactory(Plugin plugin, ErrorReporter errorReporter, ListenerManager listenerManager) { this.plugin = plugin; - this.channelListener = channelListener; this.errorReporter = errorReporter; + this.listenerManager = listenerManager; } /** @@ -118,7 +118,7 @@ public Injector fromPlayer(Player player) { player, networkManager, channel, - this.channelListener, + this.listenerManager, this, this.errorReporter); this.cacheInjector(player, injector); @@ -171,7 +171,7 @@ public Injector fromChannel(Channel channel) { temporaryPlayer, networkManager, channel, - this.channelListener, + this.listenerManager, this, this.errorReporter); diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java index d0e0d8392..7777b0f4c 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/NettyChannelInjector.java @@ -11,8 +11,6 @@ import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicBoolean; -import javax.annotation.Nullable; - import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -26,23 +24,18 @@ import com.comphenix.protocol.events.NetworkMarker; import com.comphenix.protocol.events.PacketContainer; import com.comphenix.protocol.events.PacketEvent; +import com.comphenix.protocol.injector.ListenerManager; import com.comphenix.protocol.injector.NetworkProcessor; -import com.comphenix.protocol.injector.netty.ChannelListener; import com.comphenix.protocol.injector.netty.Injector; import com.comphenix.protocol.injector.netty.WirePacket; import com.comphenix.protocol.injector.packet.PacketRegistry; import com.comphenix.protocol.reflect.FuzzyReflection; import com.comphenix.protocol.reflect.accessors.Accessors; import com.comphenix.protocol.reflect.accessors.FieldAccessor; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract; -import com.comphenix.protocol.utility.ByteBuddyGenerated; -import com.comphenix.protocol.utility.MinecraftFields; -import com.comphenix.protocol.utility.MinecraftMethods; import com.comphenix.protocol.utility.MinecraftProtocolVersion; import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; -import com.comphenix.protocol.wrappers.WrappedChatComponent; import com.comphenix.protocol.wrappers.WrappedGameProfile; import io.netty.channel.Channel; @@ -84,13 +77,14 @@ private static String getRandomKey() { // protocol lib stuff we need private final ErrorReporter errorReporter; + private final InjectionFactory injectionFactory; + private final ListenerManager listenerManager; private final NetworkProcessor networkProcessor; + private final PacketListenerInvoker listenerInvoker; // references private final Object networkManager; private final Channel channel; - private final ChannelListener channelListener; - private final InjectionFactory injectionFactory; private final FieldAccessor channelField; @@ -108,14 +102,13 @@ private static String getRandomKey() { private Player player; // lazy initialized fields, if we don't need them we don't bother about them - private volatile Object playerConnection; private volatile InboundProtocolReader inboundProtocolReader; public NettyChannelInjector( Player player, Object networkManager, Channel channel, - ChannelListener listener, + ListenerManager listenerManager, InjectionFactory injector, ErrorReporter errorReporter ) { @@ -125,11 +118,12 @@ public NettyChannelInjector( // protocol lib stuff this.errorReporter = errorReporter; this.networkProcessor = new NetworkProcessor(errorReporter); + this.listenerInvoker = new PacketListenerInvoker(networkManager); // references this.networkManager = networkManager; this.channel = channel; - this.channelListener = listener; + this.listenerManager = listenerManager; this.injectionFactory = injector; // register us into the channel @@ -260,7 +254,7 @@ public void close() { } @Override - public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) { + public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) { // ignore call if the injector is closed or not injected if (this.closed.get() || !this.injected) { return; @@ -277,14 +271,7 @@ public void sendServerPacket(Object packet, NetworkMarker marker, boolean filter } try { - Object playerConnection = this.getPlayerConnection(); - - // try to use the player connection if possible - if (playerConnection != null) { - MinecraftMethods.getPlayerConnectionSendMethod().invoke(playerConnection, packet); - } else { - MinecraftMethods.getNetworkManagerSendMethod().invoke(this.networkManager, packet); - } + this.listenerInvoker.send(packet); } catch (Exception exception) { this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET) .messageParam(packet, this.playerName) @@ -294,7 +281,7 @@ public void sendServerPacket(Object packet, NetworkMarker marker, boolean filter } @Override - public void receiveClientPacket(Object packet) { + public void readServerboundPacket(Object packet) { // ignore call if the injector is closed or not injected if (this.closed.get() || !this.injected) { return; @@ -303,7 +290,7 @@ public void receiveClientPacket(Object packet) { this.ensureInEventLoop(() -> { try { // try to invoke the method, this should normally not fail - MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet); + this.listenerInvoker.read(packet); } catch (Exception exception) { this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET) .messageParam(packet, this.playerName) @@ -332,6 +319,23 @@ public void sendWirePacket(WirePacket packet) { }); } + @Override + public void disconnect(String message) { + // ignore call if the injector is closed or not injected + if (this.closed.get() || !this.injected) { + return; + } + + try { + this.listenerInvoker.disconnect(message); + } catch (Exception exception) { + this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DISCONNECT) + .messageParam(this.playerName, message) + .error(exception) + .build()); + } + } + @Override public Protocol getCurrentProtocol(PacketType.Sender sender) { return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.channel, sender); @@ -347,6 +351,9 @@ public Player getPlayer() { // check if the name of the player is already known to the injector if (this.playerName != null) { this.player = Bukkit.getPlayerExact(this.playerName); + if (this.player != null) { + this.injectionFactory.cacheInjector(this.player, this); + } } // either we resolved it or we didn't... @@ -355,37 +362,13 @@ public Player getPlayer() { @Override public void setPlayer(Player player) { + this.injectionFactory.invalidate(this.player, this.playerName); + this.player = player; this.playerName = player.getName(); - } - @Override - public void disconnect(String message) { - try { - Object playerConnection = this.getPlayerConnection(); - - // try to use the player connection if possible - if (playerConnection != null) { - // this method prefers the main thread so no event loop here - MethodAccessor accessor = MinecraftMethods.getPlayerConnectionDisconnectMethod(); - - // check if the parameter is a chat component - if (MinecraftReflection.isIChatBaseComponent(accessor.getMethod().getParameters()[0].getClass())) { - Object component = WrappedChatComponent.fromText(message).getHandle(); - accessor.invoke(playerConnection, component); - } else { - accessor.invoke(playerConnection, message); - } - } else { - Object component = WrappedChatComponent.fromText(message).getHandle(); - MinecraftMethods.getNetworkManagerDisconnectMethod().invoke(this.networkManager, component); - } - } catch (Exception exception) { - this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_DISCONNECT) - .messageParam(this.playerName, message) - .error(exception) - .build()); - } + this.injectionFactory.cacheInjector(player, this); + this.injectionFactory.cacheInjector(player.getName(), this); } @Override @@ -422,7 +405,7 @@ boolean hasInboundListener(PacketType type) { return type == PacketType.Handshake.Client.SET_PROTOCOL || type == PacketType.Login.Client.START || // check for plugin registed listener - this.channelListener.hasInboundListener(type); + this.listenerManager.hasInboundListener(type); } void processInbound(ChannelHandlerContext ctx, PacketContainer packet) { @@ -452,7 +435,7 @@ void processInbound(ChannelHandlerContext ctx, PacketContainer packet) { } // invoke plugin listeners - if (this.channelListener.hasInboundListener(packet.getType())) { + if (this.listenerManager.hasInboundListener(packet.getType())) { this.processInboundInternal(ctx, packet); } else { ctx.fireChannelRead(packet.getHandle()); @@ -460,21 +443,18 @@ void processInbound(ChannelHandlerContext ctx, PacketContainer packet) { } private void processInboundInternal(ChannelHandlerContext ctx, PacketContainer packetContainer) { - if (this.channelListener.hasMainThreadListener(packetContainer.getType()) && !Bukkit.isPrimaryThread()) { + if (this.listenerManager.hasMainThreadListener(packetContainer.getType()) && !Bukkit.isPrimaryThread()) { // not on the main thread but we are required to be reschedule the packet on the // main thread ProtocolLibrary.getScheduler().runTask(() -> this.processInboundInternal(ctx, packetContainer)); return; } - // call packet handlers, a null result indicates that we shouldn't change - // anything - PacketEvent event = this.channelListener.onPacketReceiving(this, packetContainer, null); - if (event == null) { - this.ensureInEventLoop(ctx.channel().eventLoop(), () -> ctx.fireChannelRead(packetContainer.getHandle())); - return; - } + // create event and invoke listeners + PacketEvent event = PacketEvent.fromClient(this, packetContainer, this.player); + this.listenerManager.invokeInboundPacketListeners(event); + // get packet of event Object packet = event.getPacket().getHandle(); // fire the intercepted packet down the pipeline if it wasn't cancelled and isn't null @@ -531,25 +511,23 @@ T processOutbound(T action) { } // no listener and no marker - no magic :) - if (!this.channelListener.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) { + if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) { return action; } // ensure that we are on the main thread if we need to - if (this.channelListener.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) { + if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) { // not on the main thread but we are required to be - re-schedule the packet on the main thread - ProtocolLibrary.getScheduler().runTask(() -> this.sendServerPacket(packet, null, true)); + ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, null, true)); return null; } - // call all listeners which are listening to the outbound packet, if any - // null indicates that no listener was affected by the packet, meaning that we can directly send the original packet + // create event and invoke listeners PacketContainer packetContainer = new PacketContainer(packetType, packet); - PacketEvent event = this.channelListener.onPacketSending(this, packetContainer, marker); - if (event == null) { - return action; - } + PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player); + this.listenerManager.invokeOutboundPacketListeners(event); + // get packet of event Object interceptedPacket = event.getPacket().getHandle(); // if the event wasn't cancelled by this action we must recheck if the packet changed during the method call @@ -618,30 +596,6 @@ private FieldAccessor lookupPacketAccessor(Object action) { }); } - /** - * Returns the PlayerConnection or null if the player instance is null or a - * temporary player - * - * @return the PlayerConnection or null - */ - @Nullable - private Object getPlayerConnection() { - // resolve the player connection if needed - if (this.playerConnection == null) { - Player target = this.getPlayer(); - // if player is null or temporary return null - if (target == null || target instanceof ByteBuddyGenerated) { - return null; - } - - // this can in some cases still return null because the connection isn't set - // during the configuration phase but the player instance got created - this.playerConnection = MinecraftFields.getPlayerConnection(target); - } - - return this.playerConnection; - } - private void ensureInEventLoop(Runnable runnable) { this.ensureInEventLoop(this.channel.eventLoop(), runnable); } diff --git a/src/main/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvoker.java b/src/main/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvoker.java new file mode 100644 index 000000000..126d3c39a --- /dev/null +++ b/src/main/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvoker.java @@ -0,0 +1,237 @@ +package com.comphenix.protocol.injector.netty.channel; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.lang.reflect.Parameter; +import java.util.List; +import java.util.concurrent.atomic.AtomicReference; + +import com.comphenix.protocol.ProtocolLogger; +import com.comphenix.protocol.reflect.FuzzyReflection; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.WrappedChatComponent; + +import io.netty.channel.ChannelHandlerContext; + +/** + * This class facilitates the invocation of methods on the current packet listener. + * It attempts to execute the send, read, and disconnect + * methods and, upon failure (either due to the absence of the method or the packet + * listener being of an incorrect type), it delegates the call to the network manager. + * + *

Supported packet listener types include CONFIGURATION and PLAY. If the packet + * listener does not match these types, or if the required method is missing, the + * operation falls back to similar methods available in the network manager. + * + *

It is important to note that this class does not handle exceptions internally. + * Instead, it propagates them to the caller. During the initialization phase, the + * class will throw an exception if the necessary methods in the network manager are + * not available, ensuring that these dependencies are addressed early in the runtime. + */ +public class PacketListenerInvoker { + + private static final Class PACKET_LISTENER_CLASS = MinecraftReflection.getMinecraftClass("network.PacketListener", "PacketListener"); + private static final Class GAME_PACKET_LISTENER_CLASS = MinecraftReflection.getPlayerConnectionClass(); + private static final Class COMMON_PACKET_LISTENER_CLASS = MinecraftReflection.getNullableNMS("server.network.ServerCommonPacketListenerImpl"); + private static final Class PREFERRED_PACKET_LISTENER_CLASS = COMMON_PACKET_LISTENER_CLASS != null ? COMMON_PACKET_LISTENER_CLASS : GAME_PACKET_LISTENER_CLASS; + + private static final MethodAccessor PACKET_LISTENER_SEND = getPacketListenerSend(); + private static final MethodAccessor PACKET_LISTENER_DISCONNECT = getPacketListenerDisconnect(); + private static final boolean DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT = doesPacketListenerDisconnectUseComponent(); + + private static final MethodAccessor NETWORK_MANAGER_SEND = getNetworkManagerSend(); + private static final MethodAccessor NETWORK_MANAGER_READ = getNetworkManagerRead(); + private static final MethodAccessor NETWORK_MANAGER_DISCONNECT = getNetworkManagerDisconnect(); + private static final MethodAccessor NETWORK_MANAGER_PACKET_LISTENER = getNetworkManagerPacketListener(); + + public static void ensureStaticInitializedWithoutError() { + } + + private static MethodAccessor getPacketListenerSend() { + FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS); + + List send = packetListener.getMethodList(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeVoid() + .parameterCount(1) + .parameterExactType(MinecraftReflection.getPacketClass(), 0) + .build()); + + if (send.isEmpty()) { + ProtocolLogger.debug("Can't get packet listener send method"); + return null; + } + + return Accessors.getMethodAccessor(send.get(0)); + } + + private static MethodAccessor getPacketListenerDisconnect() { + FuzzyReflection packetListener = FuzzyReflection.fromClass(PREFERRED_PACKET_LISTENER_CLASS); + + List disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeVoid() + .parameterCount(1) + .parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0) + .build()); + + if (disconnect.isEmpty()) { + disconnect = packetListener.getMethodList(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeVoid() + .nameRegex("disconnect.*") + .parameterCount(1) + .parameterExactType(String.class, 0) + .build()); + } + + if (disconnect.isEmpty()) { + ProtocolLogger.debug("Can't get packet listener disconnect method"); + return null; + } + + return Accessors.getMethodAccessor(disconnect.get(0)); + } + + private static boolean doesPacketListenerDisconnectUseComponent() { + if (PACKET_LISTENER_DISCONNECT != null) { + Parameter reason = PACKET_LISTENER_DISCONNECT.getMethod().getParameters()[0]; + return MinecraftReflection.isIChatBaseComponent(reason.getClass()); + } + return false; + } + + private static MethodAccessor getNetworkManagerSend() { + FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass()); + + Method send = networkManager.getMethod(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeVoid() + .parameterCount(1) + .parameterExactType(MinecraftReflection.getPacketClass(), 0) + .build()); + + return Accessors.getMethodAccessor(send); + } + + private static MethodAccessor getNetworkManagerRead() { + FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass(), true); + + Method read = networkManager + .getMethodByParameters("read", ChannelHandlerContext.class, MinecraftReflection.getPacketClass()); + + return Accessors.getMethodAccessor(read); + } + + private static MethodAccessor getNetworkManagerDisconnect() { + FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass()); + + Method disconnect = networkManager.getMethod(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeVoid() + .parameterCount(1) + .parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0) + .build()); + + return Accessors.getMethodAccessor(disconnect); + } + + private static MethodAccessor getNetworkManagerPacketListener() { + FuzzyReflection networkManager = FuzzyReflection.fromClass(MinecraftReflection.getNetworkManagerClass()); + + Method packetListener = networkManager.getMethod(FuzzyMethodContract.newBuilder() + .banModifier(Modifier.STATIC) + .returnTypeExact(PACKET_LISTENER_CLASS) + .parameterCount(0) + .build()); + + return Accessors.getMethodAccessor(packetListener); + } + + private final Object networkManager; + private final AtomicReference packetListener = new AtomicReference<>(null); + + PacketListenerInvoker(Object networkManager) { + if (!MinecraftReflection.is(MinecraftReflection.getNetworkManagerClass(), networkManager)) { + throw new IllegalArgumentException("Given NetworkManager isn't an isntance of NetworkManager"); + } + this.networkManager = networkManager; + } + + /** + * Retrieves the current packet listener associated with this network manager. + * + *

This method ensures thread-safety and returns the packet listener only if it is an + * instance of the preferred class. If the packet listener has changed or does not match + * the preferred class, it returns {@code null}. + * + * @return the current packet listener if it meets the required criteria, otherwise {@code null}. + */ + private Object getPacketListener() { + // Retrieve the current packet listener from the network manager using reflection. + Object packetListener = NETWORK_MANAGER_PACKET_LISTENER.invoke(this.networkManager); + + // Perform a thread-safe check to see if the packet listener has changed since the last retrieval. + if (!this.packetListener.compareAndSet(packetListener, packetListener)) { + // If the packet listener has changed, attempt to update the cached listener to the new instance, + // or invalidate the cached object if it does not match the preferred type. + if (PREFERRED_PACKET_LISTENER_CLASS.isInstance(packetListener)) { + this.packetListener.set(packetListener); + } else { + this.packetListener.set(null); + } + } + + // Return the currently cached packet listener, which may be null if it does not match the preferred type. + return this.packetListener.get(); + } + + /** + * Sends a packet using the current packet listener if available and valid; otherwise, + * falls back to the network manager. + * + * @param packet The packet to be sent. + */ + public void send(Object packet) { + Object packetListener = this.getPacketListener(); + if (PACKET_LISTENER_SEND != null && packetListener != null) { + PACKET_LISTENER_SEND.invoke(packetListener, packet); + } else { + NETWORK_MANAGER_SEND.invoke(this.networkManager, packet); + } + } + + /** + * Reads a packet directly using the network manager. + * + * @param packet The packet to be read. + */ + public void read(Object packet) { + NETWORK_MANAGER_READ.invoke(this.networkManager, null, packet); + } + + /** + * Disconnects the player using the current packet listener if available and valid; otherwise, + * falls back to the network manager. + * + * @param reason The reason for the disconnection. + */ + public void disconnect(String reason) { + Object packetListener = this.getPacketListener(); + boolean hasPacketListener = PACKET_LISTENER_DISCONNECT != null && packetListener != null; + + Object wrapped = reason; + if (!hasPacketListener || DOES_PACKET_LISTENER_DISCONNECT_USE_COMPONENT) { + wrapped = WrappedChatComponent.fromText(reason).getHandle(); + } + + if (hasPacketListener) { + PACKET_LISTENER_DISCONNECT.invoke(packetListener, wrapped); + } else { + NETWORK_MANAGER_DISCONNECT.invoke(this.networkManager, wrapped); + } + } +} diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java index db433e680..a17c7d851 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/InjectionChannelInboundHandler.java @@ -1,5 +1,6 @@ package com.comphenix.protocol.injector.netty.manager; +import com.comphenix.protocol.error.ErrorReporter; import com.comphenix.protocol.error.Report; import com.comphenix.protocol.error.ReportType; import com.comphenix.protocol.injector.netty.channel.InjectionFactory; @@ -11,15 +12,15 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s."); - private final InjectionFactory factory; - private final NetworkManagerInjector listener; + private final ErrorReporter errorReporter; + private final InjectionFactory injectionFactory; public InjectionChannelInboundHandler( - InjectionFactory factory, - NetworkManagerInjector listener + ErrorReporter errorReporter, + InjectionFactory injectionFactory ) { - this.factory = factory; - this.listener = listener; + this.errorReporter = errorReporter; + this.injectionFactory = injectionFactory; } @Override @@ -32,11 +33,11 @@ public void channelActive(ChannelHandlerContext ctx) { // We're first checking if the factory is still open, just might be a delay between accepting the connection // (which adds this handler to the pipeline) and the actual channelActive call. If the injector is closed at // that point we might accidentally trigger class loads which result in exceptions. - if (!this.factory.isClosed()) { + if (!this.injectionFactory.isClosed()) { try { - this.factory.fromChannel(ctx.channel()).inject(); + this.injectionFactory.fromChannel(ctx.channel()).inject(); } catch (Exception exception) { - this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL) + this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL) .messageParam(ctx.channel()) .error(exception) .build()); diff --git a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java index 2cc316dab..17b98d4bf 100644 --- a/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java +++ b/src/main/java/com/comphenix/protocol/injector/netty/manager/NetworkManagerInjector.java @@ -10,14 +10,9 @@ import org.bukkit.entity.Player; import org.bukkit.plugin.Plugin; -import com.comphenix.protocol.PacketType; import com.comphenix.protocol.ProtocolLogger; import com.comphenix.protocol.error.ErrorReporter; -import com.comphenix.protocol.events.NetworkMarker; -import com.comphenix.protocol.events.PacketContainer; -import com.comphenix.protocol.events.PacketEvent; -import com.comphenix.protocol.injector.ListenerInvoker; -import com.comphenix.protocol.injector.netty.ChannelListener; +import com.comphenix.protocol.injector.ListenerManager; import com.comphenix.protocol.injector.netty.Injector; import com.comphenix.protocol.injector.netty.channel.InjectionFactory; import com.comphenix.protocol.reflect.FuzzyReflection; @@ -30,7 +25,7 @@ import io.netty.channel.ChannelFuture; -public class NetworkManagerInjector implements ChannelListener { +public class NetworkManagerInjector { private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject"; @@ -38,90 +33,26 @@ public class NetworkManagerInjector implements ChannelListener { private final Set> overriddenLists = new HashSet<>(); private final ErrorReporter errorReporter; - private final ListenerInvoker listenerInvoker; private final InjectionFactory injectionFactory; // netty handler private final InjectionChannelInitializer pipelineInjectorHandler; - private boolean debug = false; - // status of this injector private boolean closed = false; private boolean injected = false; - public NetworkManagerInjector(Plugin plugin, ListenerInvoker listenerInvoker, ErrorReporter reporter) { + public NetworkManagerInjector(Plugin plugin, ListenerManager listenerManager, ErrorReporter reporter) { this.errorReporter = reporter; - this.listenerInvoker = listenerInvoker; - this.injectionFactory = new InjectionFactory(plugin, this, reporter); + this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager); // hooking netty handlers InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler( - this.injectionFactory, - this); + this.errorReporter, + this.injectionFactory); this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler); } - @Override - public PacketEvent onPacketSending(Injector injector, PacketContainer packet, NetworkMarker marker) { - // check if we need to intercept the packet - Class packetClass = packet.getHandle().getClass(); - if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || hasOutboundListener(packet.getType())) { - // wrap packet and construct the event - PacketEvent packetEvent = PacketEvent.fromServer(this, packet, marker, injector.getPlayer()); - - // post to all listeners, then return the packet event we constructed - this.listenerInvoker.invokePacketSending(packetEvent); - return packetEvent; - } - - // no listener so there is no change we need to apply - return null; - } - - @Override - public PacketEvent onPacketReceiving(Injector injector, PacketContainer packet, NetworkMarker marker) { - if (marker != null || hasInboundListener(packet.getType())) { - PacketEvent packetEvent = PacketEvent.fromClient(this, packet, marker, injector.getPlayer()); - - // post to all listeners, then return the packet event we constructed - this.listenerInvoker.invokePacketReceiving(packetEvent); - return packetEvent; - } - - // no listener so there is no change we need to apply - return null; - } - - @Override - public boolean hasInboundListener(PacketType packetType) { - return this.listenerInvoker.hasInboundListener(packetType); - } - - @Override - public boolean hasOutboundListener(PacketType packetType) { - return this.listenerInvoker.hasOutboundListener(packetType); - } - - @Override - public boolean hasMainThreadListener(PacketType packetType) { - return this.listenerInvoker.hasMainThreadListener(packetType); - } - - @Override - public ErrorReporter getReporter() { - return this.errorReporter; - } - - @Override - public boolean isDebug() { - return this.debug; - } - - public void setDebug(boolean debug) { - this.debug = debug; - } - public Injector getInjector(Player player) { return this.injectionFactory.fromPlayer(player); } diff --git a/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java index 91511f681..ed05bc79f 100644 --- a/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java +++ b/src/main/java/com/comphenix/protocol/injector/temporary/TemporaryPlayerFactory.java @@ -170,7 +170,7 @@ else if (methodName.equals("getPlayer")) { */ private static Object sendMessage(Injector injector, String message) { for (PacketContainer packet : ChatExtensions.createChatPackets(message)) { - injector.sendServerPacket(packet.getHandle(), null, false); + injector.sendClientboundPacket(packet.getHandle(), null, false); } return null; diff --git a/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java b/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java index f09bb1470..9da3eab61 100644 --- a/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java +++ b/src/main/java/com/comphenix/protocol/reflect/accessors/ConstructorAccessor.java @@ -4,6 +4,21 @@ public interface ConstructorAccessor { + /** + * NoOp Accessor, does what is says: nothing. + */ + static final ConstructorAccessor NO_OP_ACCESSOR = new ConstructorAccessor() { + @Override + public Object invoke(Object... args) { + return null; + } + + @Override + public Constructor getConstructor() { + return null; + } + }; + /** * Invoke the underlying constructor. * diff --git a/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java b/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java index 1be5d64a5..c53ae1f36 100644 --- a/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java +++ b/src/main/java/com/comphenix/protocol/reflect/instances/MinecraftGenerator.java @@ -1,17 +1,19 @@ package com.comphenix.protocol.reflect.instances; -import com.comphenix.protocol.reflect.accessors.Accessors; -import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; -import com.comphenix.protocol.reflect.accessors.MethodAccessor; -import com.comphenix.protocol.utility.MinecraftReflection; -import com.comphenix.protocol.wrappers.BukkitConverters; import java.util.HashMap; import java.util.Map; import java.util.UUID; + import org.bukkit.Material; import org.bukkit.entity.EntityType; import org.bukkit.inventory.ItemStack; +import com.comphenix.protocol.reflect.accessors.Accessors; +import com.comphenix.protocol.reflect.accessors.ConstructorAccessor; +import com.comphenix.protocol.reflect.accessors.MethodAccessor; +import com.comphenix.protocol.utility.MinecraftReflection; +import com.comphenix.protocol.wrappers.BukkitConverters; + public class MinecraftGenerator { // system unique id representation @@ -58,14 +60,20 @@ public class MinecraftGenerator { try { String name = type.getName(); if (name.contains("it.unimi.dsi.fastutil")) { - Class clz = Class.forName(name.replace("Map", "OpenHashMap")); - return Accessors.getConstructorAccessorOrNull(clz); + // convert interface maps to OpenHashMaps + if (!name.endsWith("OpenHashMap")) { + name = name.replace("Map", "OpenHashMap"); + } + + Class hashMapClass = Class.forName(name); + return Accessors.getConstructorAccessorOrNull(hashMapClass); } } catch (Exception ignored) { } - return null; + return ConstructorAccessor.NO_OP_ACCESSOR; }); - if (ctor != null) { + + if (ctor != ConstructorAccessor.NO_OP_ACCESSOR) { try { return ctor.invoke(); } catch (Exception ignored) { diff --git a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java index 2e06b60bb..a0d05ce07 100644 --- a/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java +++ b/src/main/java/com/comphenix/protocol/utility/MinecraftReflection.java @@ -374,7 +374,7 @@ public static boolean is(Class clazz, Object object) { return false; } - // check for accidential class objects + // check for accidental class objects if (object instanceof Class) { return clazz.isAssignableFrom((Class) object); } diff --git a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java index 1a99abec1..af6b5384f 100644 --- a/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java +++ b/src/main/java/com/comphenix/protocol/wrappers/EnumWrappers.java @@ -11,6 +11,10 @@ import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.apache.commons.lang.Validate; +import org.bukkit.ChatColor; +import org.bukkit.GameMode; + import com.comphenix.protocol.PacketType; import com.comphenix.protocol.PacketType.Protocol; import com.comphenix.protocol.ProtocolLogger; @@ -23,10 +27,6 @@ import com.comphenix.protocol.utility.MinecraftReflection; import com.comphenix.protocol.utility.MinecraftVersion; -import org.apache.commons.lang.Validate; -import org.bukkit.ChatColor; -import org.bukkit.GameMode; - /** * Represents a generic enum converter. * @author Kristian @@ -623,6 +623,21 @@ public static ChatFormatting fromBukkit(ChatColor color) { } } + /** + * Represents the client's intentions when connecting to the server. Previously, + * the game utilized the {@code EnumProtocol}, which included the additional states + * HANDSHAKE and PLAY. These states are not incorporated in the ClientIntent enum + * as they were never valid values for client intent under the current or + * past implementations. + * + * @since 1.20.5 + */ + public enum ClientIntent { + STATUS, + LOGIN, + TRANSFER; + } + private static Class PROTOCOL_CLASS = null; private static Class CLIENT_COMMAND_CLASS = null; private static Class CHAT_VISIBILITY_CLASS = null; @@ -647,6 +662,7 @@ public static ChatFormatting fromBukkit(ChatColor color) { private static Class DISPLAY_SLOT_CLASS = null; private static Class RENDER_TYPE_CLASS = null; private static Class CHAT_FORMATTING_CLASS = null; + private static Class CLIENT_INTENT_CLASS = null; private static boolean INITIALIZED = false; private static Map, EquivalentConverter> FROM_NATIVE = new HashMap<>(); @@ -744,6 +760,8 @@ private static void initialize() { "IScoreboardCriteria$EnumScoreboardHealthDisplay"); CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat"); + CLIENT_INTENT_CLASS = getEnum(PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass(), 0); + associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter()); associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter()); associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter()); @@ -767,6 +785,7 @@ private static void initialize() { associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter()); associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter()); associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter()); + associate(CLIENT_INTENT_CLASS, ClientIntent.class, getClientIntentConverter()); if (ENTITY_POSE_CLASS != null) { associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter()); @@ -933,6 +952,11 @@ public static Class getChatFormattingClass() { return CHAT_FORMATTING_CLASS; } + public static Class getClientIntentClass() { + initialize(); + return CLIENT_INTENT_CLASS; + } + // Get the converters public static EquivalentConverter getProtocolConverter() { return new EnumConverter<>(getProtocolClass(), Protocol.class); @@ -1026,6 +1050,10 @@ public static EquivalentConverter getChatFormattingConverter() { return new EnumConverter<>(getChatFormattingClass(), ChatFormatting.class); } + public static EquivalentConverter getClientIntentConverter() { + return new EnumConverter<>(getClientIntentClass(), ClientIntent.class); + } + /** * @since 1.13+ * @return {@link EnumConverter} or null (if bellow 1.13 / nms EnumPose class cannot be found) diff --git a/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java b/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java deleted file mode 100644 index 776174ee1..000000000 --- a/src/test/java/com/comphenix/protocol/concurrency/BlockingHashMapTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * ProtocolLib - Bukkit server library that allows access to the Minecraft protocol. - * Copyright (C) 2012 Kristian S. Stangeland - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 2 of - * the License, or (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; - * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License along with this program; - * if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA - * 02111-1307 USA - */ - -package com.comphenix.protocol.concurrency; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import org.junit.jupiter.api.Test; - -public class BlockingHashMapTest { - - @Test - public void test() throws InterruptedException, ExecutionException { - - final BlockingHashMap map = BlockingHashMap.create(); - - ExecutorService service = Executors.newSingleThreadExecutor(); - - // Create a reader - Future future = service.submit(() -> { - // Combine for easy reading - return map.get(0) + map.get(1); - }); - - // Wait a bit - Thread.sleep(50); - - // Insert values - map.put(0, "hello "); - map.put(1, "world"); - - // Wait for the other thread to complete - assertEquals(future.get(), "hello world"); - } -} diff --git a/src/test/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvokerTest.java b/src/test/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvokerTest.java new file mode 100644 index 000000000..370b9b2c1 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/injector/netty/channel/PacketListenerInvokerTest.java @@ -0,0 +1,21 @@ +package com.comphenix.protocol.injector.netty.channel; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.comphenix.protocol.BukkitInitialization; + +public class PacketListenerInvokerTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testInitialization() { + assertDoesNotThrow(() -> PacketListenerInvoker.ensureStaticInitializedWithoutError()); + } +} diff --git a/src/test/java/com/comphenix/protocol/reflect/instances/MinecraftGeneratorTest.java b/src/test/java/com/comphenix/protocol/reflect/instances/MinecraftGeneratorTest.java new file mode 100644 index 000000000..b07865350 --- /dev/null +++ b/src/test/java/com/comphenix/protocol/reflect/instances/MinecraftGeneratorTest.java @@ -0,0 +1,36 @@ +package com.comphenix.protocol.reflect.instances; + +import static org.junit.jupiter.api.Assertions.assertNotNull; + +import java.util.UUID; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import com.comphenix.protocol.BukkitInitialization; +import com.comphenix.protocol.PacketType; + +import it.unimi.dsi.fastutil.ints.Int2ObjectMap; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; +import net.minecraft.core.NonNullList; +import net.minecraft.world.entity.EntityTypes; +import net.minecraft.world.item.ItemStack; + +public class MinecraftGeneratorTest { + + @BeforeAll + public static void beforeClass() { + BukkitInitialization.initializeAll(); + } + + @Test + public void testInstantiation() { + assertNotNull(MinecraftGenerator.INSTANCE.create(UUID.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(PacketType.Protocol.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(ItemStack.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(EntityTypes.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(Int2ObjectMap.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(Int2ObjectOpenHashMap.class)); + assertNotNull(MinecraftGenerator.INSTANCE.create(NonNullList.class)); + } +}