Compare commits

..
Author SHA1 Message Date
Dan Mulloy 4649daef82 fix: fail gracefully for unknown protocol 2024-06-14 13:59:53 -05:00
Dan Mulloy 70d47ce622 wip: mojang mappings 2024-06-13 20:17:15 -05:00
72 changed files with 2447 additions and 1532 deletions

No files matched your search

+3 -3
View File
@@ -9,10 +9,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
@@ -22,7 +22,7 @@ jobs:
run: ./gradlew build shadowJar --no-daemon
- name: Upload plugin file
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@v2
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
+4 -4
View File
@@ -20,17 +20,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v3
- name: Setup java
uses: actions/setup-java@v4
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v3
uses: github/codeql-action/init@v2
with:
languages: 'java'
@@ -38,4 +38,4 @@ jobs:
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3
uses: github/codeql-action/analyze@v2
+3 -3
View File
@@ -11,7 +11,7 @@ description = 'Provides access to the Minecraft protocol'
def isSnapshot = version.endsWith('-SNAPSHOT')
repositories {
// mavenLocal() // can speed up build, but may fail in CI
mavenLocal() // can speed up build, but may fail in CI
mavenCentral()
maven {
@@ -35,7 +35,7 @@ repositories {
dependencies {
implementation 'net.bytebuddy:byte-buddy:1.14.14'
compileOnly 'org.spigotmc:spigot-api:1.20.6-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT:remapped-mojang'
compileOnly 'io.netty:netty-all:4.0.23.Final'
compileOnly 'net.kyori:adventure-text-serializer-gson:4.14.0'
compileOnly 'com.googlecode.json-simple:json-simple:1.1.1'
@@ -46,7 +46,7 @@ dependencies {
testImplementation 'org.mockito:mockito-core:5.6.0'
testImplementation 'io.netty:netty-common:4.1.97.Final'
testImplementation 'io.netty:netty-transport:4.1.97.Final'
testImplementation 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT'
testImplementation 'org.spigotmc:spigot:1.21-R0.1-SNAPSHOT:remapped-mojang'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.14.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.14.0'
}
@@ -479,8 +479,12 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
public static final PacketType UPDATE_SIGN = new PacketType(PROTOCOL, SENDER, 0x35, "SignUpdate", "UpdateSign", "CPacketUpdateSign");
public static final PacketType ARM_ANIMATION = new PacketType(PROTOCOL, SENDER, 0x36, "Swing", "ArmAnimation", "CPacketAnimation");
public static final PacketType SPECTATE = new PacketType(PROTOCOL, SENDER, 0x37, "TeleportToEntity", "Spectate", "CPacketSpectate");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem");
public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn");
public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem");
// public static final PacketType USE_ITEM_ON = new PacketType(PROTOCOL, SENDER, 0x38, "UseItemOn", "BlockPlace", "CPacketPlayerTryUseItemOnBlock");
// public static final PacketType USE_ITEM = new PacketType(PROTOCOL, SENDER, 0x39, "UseItem", "CPacketPlayerTryUseItem");
/**
* @deprecated Removed in 1.17
@@ -607,6 +607,8 @@ public class AsyncListenerHandler {
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
// Minecraft doesn't want your Exception.
filterManager.getErrorReporter().reportMinimal(listener.getPlugin(), methodName, e);
@@ -0,0 +1,494 @@
/*
* 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.
* <p>
* Note that this implementation is not thread safe.
*
* @author Kristian
*
* @param <TKey> - type of the key. Must implement Comparable.
* @param <TValue> - type of the value to associate.
*/
public abstract class AbstractIntervalTree<TKey extends Comparable<TKey>, TValue> {
protected enum State {
OPEN,
CLOSE,
BOTH
}
/**
* Represents a range and a value in this interval tree.
*/
public class Entry implements Map.Entry<Range<TKey>, 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<TKey> 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<TKey, EndPoint> 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<Entry> 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<Entry> remove(TKey lowerBound, TKey upperBound, boolean preserveDifference) {
checkBounds(lowerBound, upperBound);
NavigableMap<TKey, EndPoint> 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<Entry> resized = new HashSet<>();
Set<Entry> 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.
* <p>
* 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<Entry> entrySet() {
// Don't mind the Java noise
Set<Entry> 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<Entry> destination, NavigableMap<TKey, EndPoint> map) {
Map.Entry<TKey, EndPoint> last = null;
for (Map.Entry<TKey, EndPoint> 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<TKey, TValue> 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<TKey, EndPoint> 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<TKey, EndPoint> 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<TKey, EndPoint> 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<TKey, EndPoint> 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<Entry> added) {
for (Entry entry : added) {
onEntryAdded(entry);
}
}
private void invokeEntryRemoved(Set<Entry> 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);
}
@@ -0,0 +1,266 @@
/*
* 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.
* <p>
* Values are stored as weak references, and will be automatically removed once they've all been dereferenced.
* <p>
* @author Kristian
*
* @param <TKey> - type of the key.
* @param <TValue> - type of the value.
*/
public class BlockingHashMap<TKey, TValue> {
// Map of values
private final ConcurrentMap<TKey, TValue> backingMap;
// Map of locked objects
private final ConcurrentMap<TKey, Object> locks;
/**
* Retrieve a cache loader that will always throw an exception.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return An invalid cache loader.
*/
public static <TKey, TValue> CacheLoader<TKey, TValue> newInvalidCacheLoader() {
return new CacheLoader<TKey, TValue>() {
@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.<TKey, TValue>newBuilder().
weakValues().
removalListener(
new RemovalListener<TKey, TValue>() {
@Override
public void onRemoval(RemovalNotification<TKey, TValue> entry) {
// Clean up locks too
if (entry.getCause() != RemovalCause.REPLACED) {
locks.remove(entry.getKey());
}
}
}).
build(BlockingHashMap.<TKey, TValue> newInvalidCacheLoader());
// Normal concurrent hash map
locks = new ConcurrentHashMap<>();
}
/**
* Initialize a new map.
* @param <TKey> Type of the key
* @param <TValue> Type of the value
* @return The created map.
*/
public static <TKey, TValue> BlockingHashMap<TKey, TValue> 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.
* <p>
* 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.
* <p>
* 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<TValue> values() {
return backingMap.values();
}
public Set<TKey> 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;
}
}
@@ -30,12 +30,8 @@ import java.util.concurrent.atomic.AtomicInteger;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolConfig;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.collections.ExpireHashMap;
@@ -45,6 +41,11 @@ import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
/**
* Internal class used to handle exceptions.
*
@@ -478,7 +479,7 @@ public class DetailedErrorReporter implements ErrorReporter {
} catch (LinkageError ex) {
// Apache is probably missing
apacheCommonsMissing = true;
} catch (OutOfMemoryError e) {
} catch (ThreadDeath | OutOfMemoryError e) {
throw e;
} catch (Throwable ex) {
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
@@ -1,18 +1,17 @@
package com.comphenix.protocol.events;
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 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 org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -26,49 +25,10 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
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;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.*;
public abstract class AbstractStructure {
protected transient Object handle;
@@ -1174,17 +1134,6 @@ public abstract class AbstractStructure {
);
}
/**
* Retrieve a read/write structure for the ClientIntent enum used in Handshake/SetProtocol packet
* @return A modifier for Protocol enum fields.
*/
public StructureModifier<EnumWrappers.ClientIntent> 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
@@ -13,7 +13,6 @@ public enum ListenerOptions {
* Disable the automatic game phase detection that will normally force {@link GamePhase#LOGIN} when a packet ID is
* known to be transmitted during login.
*/
@Deprecated
DISABLE_GAMEPHASE_DETECTION,
/**
@@ -17,12 +17,7 @@
package com.comphenix.protocol.events;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
@@ -95,6 +90,20 @@ public class ListeningWhitelist {
return new Builder(template);
}
/**
* Construct a copy of a given enum.
*
* @param options - the options to copy, or NULL to indicate the empty set.
* @return A copy of the enum set.
*/
private static <T extends Enum<T>> EnumSet<T> safeEnumSet(Collection<T> options, Class<T> enumClass) {
if (options != null && !options.isEmpty()) {
return EnumSet.copyOf(options);
} else {
return EnumSet.noneOf(enumClass);
}
}
/**
* Construct a copy of a given set.
*
@@ -302,7 +311,6 @@ public class ListeningWhitelist {
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhase(GamePhase gamePhase) {
this.gamePhase = gamePhase;
return this;
@@ -313,7 +321,6 @@ public class ListeningWhitelist {
*
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
@@ -152,7 +152,6 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return lastSeen;
}
@Override
public Location getRespawnLocation() {
return null;
}
@@ -241,11 +240,21 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return uuid;
}
/**
* @return
*/
@NotNull
@Override
public @NotNull PlayerProfile getPlayerProfile() {
public PlayerProfile getPlayerProfile() {
return null;
}
/*
public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() {
return null;
}
*/
@Override
public String getName() {
return name;
@@ -288,6 +297,11 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return online;
}
// @Override
public boolean isConnected() {
return false;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
@@ -22,7 +22,6 @@ package com.comphenix.protocol.injector;
*
* @author Kristian
*/
@Deprecated
public enum GamePhase {
/**
* Only listen for packets sent or received before a player has logged in.
@@ -25,7 +25,7 @@ import com.comphenix.protocol.events.PacketEvent;
*
* @author Kristian
*/
public interface ListenerManager {
public interface ListenerInvoker {
boolean hasInboundListener(PacketType packetType);
@@ -38,12 +38,24 @@ public interface ListenerManager {
*
* @param event - the packet event to invoke.
*/
void invokeInboundPacketListeners(PacketEvent event);
void invokePacketReceiving(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
void invokeOutboundPacketListeners(PacketEvent event);
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);
}
@@ -1,7 +1,5 @@
package com.comphenix.protocol.injector;
import java.util.Deque;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.error.ErrorReporter;
@@ -9,6 +7,7 @@ import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketPostListener;
import com.comphenix.protocol.events.ScheduledPacket;
import java.util.Deque;
/**
* Represents a processor for network markers.
@@ -44,7 +43,7 @@ public class NetworkProcessor {
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError e) {
} catch (OutOfMemoryError | ThreadDeath e) {
throw e;
} catch (Throwable e) {
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
@@ -172,6 +172,8 @@ public class PacketConstructor {
result = unwrapper.unwrapItem(values[i]);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
lastException = e;
}
@@ -46,10 +46,15 @@ import com.comphenix.protocol.injector.collection.PacketListenerSet;
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.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler.ConflictStrategy;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
public class PacketFilterManager implements ListenerManager, InternalManager {
import io.netty.channel.Channel;
public class PacketFilterManager implements ListenerInvoker, InternalManager {
// plugin verifier reports
private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s");
@@ -75,6 +80,7 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
private final Set<PacketListener> registeredListeners;
// injectors
private final PlayerInjectionHandler playerInjectionHandler;
private final NetworkManagerInjector networkManagerInjector;
// status of this manager
@@ -105,8 +111,10 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
// injectors
this.networkManagerInjector = new NetworkManagerInjector(
builder.getLibrary(),
builder.getServer(),
this,
builder.getReporter());
this.playerInjectionHandler = this.networkManagerInjector.getPlayerInjectionHandler();
// ensure that all packet types are loaded and synced
PacketRegistry.getClientPacketTypes();
@@ -114,7 +122,7 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
// hook into all connected players
for (Player player : this.server.getOnlinePlayers()) {
this.networkManagerInjector.getInjector(player).inject();
this.playerInjectionHandler.injectPlayer(player, ConflictStrategy.BAIL_OUT);
}
}
@@ -172,7 +180,7 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
}
// process outbound
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters);
}
}
@@ -184,7 +192,13 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
@Override
public void sendWirePacket(Player receiver, WirePacket packet) {
if (!this.closed) {
this.networkManagerInjector.getInjector(receiver).sendWirePacket(packet);
// special case - we just throw the wire packet down the pipeline without processing it
Channel outboundChannel = this.playerInjectionHandler.getChannel(receiver);
if (outboundChannel == null) {
throw new IllegalArgumentException("Unable to obtain connection of player " + receiver);
}
outboundChannel.writeAndFlush(packet);
}
}
@@ -213,7 +227,7 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
if (filters) {
// post to all listeners
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
this.invokeInboundPacketListeners(event);
this.invokePacketReceiving(event);
if (event.isCancelled()) {
return;
}
@@ -226,13 +240,13 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
}
// post to the player inject, reset our cancel state change
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket);
}
}
@Override
public int getProtocolVersion(Player player) {
return this.networkManagerInjector.getInjector(player).getProtocolVersion();
return this.playerInjectionHandler.getProtocolVersion(player);
}
@Override
@@ -433,12 +447,12 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -504,19 +518,33 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
}
@Override
public void invokeInboundPacketListeners(PacketEvent event) {
public void invokePacketReceiving(PacketEvent event) {
if (!this.closed) {
this.postPacketToListeners(this.inboundListeners, event, false);
}
}
@Override
public void invokeOutboundPacketListeners(PacketEvent event) {
public void invokePacketSending(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
@@ -0,0 +1,59 @@
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.
* <p>
* 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.
* <p>
* 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();
}
@@ -1,12 +1,9 @@
package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import org.bukkit.entity.Player;
/**
* Represents an injected client connection.
@@ -15,8 +12,6 @@ import com.comphenix.protocol.events.NetworkMarker;
*/
public interface Injector {
SocketAddress getAddress();
/**
* Retrieve the current protocol version of the player.
*
@@ -28,8 +23,12 @@ public interface Injector {
* Inject the current channel.
* <p>
* Note that only active channels can be injected.
*
* @return TRUE if we injected the channel, false if we could not inject or it was already injected.
*/
void inject();
boolean inject();
void uninject();
/**
* Close the current injector.
@@ -43,13 +42,20 @@ public interface Injector {
* @param marker - the network marker.
* @param filtered - whether or not the packet is filtered.
*/
void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered);
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
void readServerboundPacket(Object packet);
void receiveClientPacket(Object packet);
void sendWirePacket(WirePacket packet);
void disconnect(String message);
/**
* Retrieve the current protocol state.
*
* @return The current protocol.
* @deprecated use {@link #getCurrentProtocol(PacketType.Sender)} instead.
*/
@Deprecated
default Protocol getCurrentProtocol() {
return this.getCurrentProtocol(PacketType.Sender.SERVER);
}
/**
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
@@ -60,6 +66,22 @@ public interface Injector {
*/
Protocol getCurrentProtocol(PacketType.Sender sender);
/**
* Retrieve the network marker associated with a given packet.
*
* @param packet - the packet.
* @return The network marker.
*/
NetworkMarker getMarker(Object packet);
/**
* Associate a given network marker with a specific packet.
*
* @param packet - the NMS packet.
* @param marker - the associated marker.
*/
void saveMarker(Object packet, NetworkMarker marker);
/**
* Retrieve the current player or temporary player associated with the injector.
*
@@ -74,7 +96,7 @@ public interface Injector {
*/
void setPlayer(Player player);
boolean isConnected();
void disconnect(String message);
/**
* Determine if the channel has already been injected.
@@ -1,14 +1,10 @@
package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.WirePacket;
import org.bukkit.entity.Player;
final class EmptyInjector implements Injector {
@@ -20,21 +16,18 @@ final class EmptyInjector implements Injector {
this.player = player;
}
@Override
public SocketAddress getAddress() {
if (this.player != null) {
return this.player.getAddress();
}
return null;
}
@Override
public int getProtocolVersion() {
return Integer.MIN_VALUE;
}
@Override
public void inject() {
public boolean inject() {
return false;
}
@Override
public void uninject() {
}
@Override
@@ -42,19 +35,11 @@ final class EmptyInjector implements Injector {
}
@Override
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
}
@Override
public void readServerboundPacket(Object packet) {
}
@Override
public void sendWirePacket(WirePacket packet) {
}
@Override
public void disconnect(String message) {
public void receiveClientPacket(Object packet) {
}
@Override
@@ -62,6 +47,15 @@ final class EmptyInjector implements Injector {
return Protocol.HANDSHAKING;
}
@Override
public NetworkMarker getMarker(Object packet) {
return null;
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
}
@Override
public Player getPlayer() {
return this.player;
@@ -73,8 +67,7 @@ final class EmptyInjector implements Injector {
}
@Override
public boolean isConnected() {
return false;
public void disconnect(String message) {
}
@Override
@@ -2,8 +2,7 @@ package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -13,38 +12,39 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
private final NettyChannelInjector injector;
private final ChannelListener channelListener;
public InboundPacketInterceptor(NettyChannelInjector injector) {
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
this.injector = injector;
this.channelListener = listener;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (MinecraftReflection.isPacketClass(msg)) {
// try get packet type
PacketType.Protocol protocol = this.injector.getInboundProtocol();
if (protocol == Protocol.UNKNOWN) {
ProtocolLogger.debug("skipping unknown inbound protocol for {0}", msg.getClass());
ctx.fireChannelRead(msg);
return;
}
// process the login if the packet is one before posting the packet to any
// handler to provide "real" data the method invocation will do nothing if the
// packet is not a login packet
this.injector.tryProcessLogin(msg);
PacketType.Protocol protocol = this.injector.getInboundProtocol();
PacketType packetType = PacketRegistry.getPacketType(protocol, msg.getClass());
if (packetType == null) {
ProtocolLogger.debug("skipping unknown inbound packet type for {0}", msg.getClass());
ctx.fireChannelRead(msg);
return;
}
// check if there are any listeners bound for the packet - if not just send the
// check if there are any listeners bound for the packet - if not just post the
// packet down the pipeline
if (!this.injector.hasInboundListener(packetType)) {
if (!this.channelListener.hasInboundListener(packetType)) {
ctx.fireChannelRead(msg);
return;
}
// don't invoke next handler and transfer packet to injector for further processing
this.injector.processInbound(ctx, new PacketContainer(packetType, msg));
// call all inbound listeners
this.injector.processInboundPacket(ctx, msg, packetType);
} else {
// just pass the message down the pipeline
ctx.fireChannelRead(msg);
@@ -14,25 +14,21 @@
*/
package com.comphenix.protocol.injector.netty.channel;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.utility.MinecraftFields;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.google.common.collect.MapMaker;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an injector factory.
@@ -43,23 +39,24 @@ import io.netty.channel.ChannelHandler;
*/
public class InjectionFactory {
// This should work as long as the injectors are, uh, injected
private final ConcurrentMap<String, Injector> nameLookup = new MapMaker().weakValues().makeMap();
private final ConcurrentMap<Player, Injector> playerLookup = new MapMaker().weakKeys().weakValues().makeMap();
// bukkit stuff
private final Plugin plugin;
private final Server server;
// protocol lib stuff
private final ErrorReporter errorReporter;
private final ListenerManager listenerManager;
// state of the factory
private boolean closed;
public InjectionFactory(Plugin plugin, ErrorReporter errorReporter, ListenerManager listenerManager) {
public InjectionFactory(Plugin plugin, Server server, ErrorReporter errorReporter) {
this.plugin = plugin;
this.server = server;
this.errorReporter = errorReporter;
this.listenerManager = listenerManager;
}
/**
@@ -75,10 +72,11 @@ public class InjectionFactory {
* Construct or retrieve a channel injector from an existing Bukkit player.
*
* @param player - the existing Bukkit player.
* @param listener - the listener.
* @return A new injector, an existing injector associated with this player, or a closed injector.
*/
@Nonnull
public Injector fromPlayer(Player player) {
public Injector fromPlayer(Player player, ChannelListener listener) {
if (this.closed) {
return new EmptyInjector(player);
}
@@ -116,9 +114,10 @@ public class InjectionFactory {
// construct a new injector as it seems like we have none yet
injector = new NettyChannelInjector(
player,
this.server,
networkManager,
channel,
this.listenerManager,
listener,
this,
this.errorReporter);
this.cacheInjector(player, injector);
@@ -156,27 +155,31 @@ public class InjectionFactory {
* Construct a new channel injector for the given channel.
*
* @param channel - the channel.
* @param listener - the listener.
* @param playerFactory - a temporary player creator.
* @return The channel injector, or a closed injector.
*/
@Nonnull
public Injector fromChannel(Channel channel) {
public Injector fromChannel(Channel channel, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
if (this.closed) {
return EmptyInjector.WITHOUT_PLAYER;
}
Object networkManager = this.findNetworkManager(channel);
Player temporaryPlayer = TemporaryPlayerFactory.createTemporaryPlayer();
Object netManager = this.findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
NettyChannelInjector injector = new NettyChannelInjector(
temporaryPlayer,
networkManager,
this.server,
netManager,
channel,
this.listenerManager,
listener,
this,
this.errorReporter);
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorForPlayer(temporaryPlayer, injector);
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
return injector;
}
@@ -232,9 +235,9 @@ public class InjectionFactory {
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
Injector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelInjector) {
return (NettyChannelInjector) injector;
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelMinimalInjector) {
return ((NettyChannelMinimalInjector) injector).getInjector();
}
return null;
@@ -247,12 +250,11 @@ public class InjectionFactory {
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (networkManagerClass.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
MinecraftReflection.getNetworkManagerClass());
if (networkManager != null) {
return networkManager;
}
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
@@ -2,17 +2,14 @@ package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.net.SocketAddress;
import java.util.Map;
import java.util.Map.Entry;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicBoolean;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
@@ -24,67 +21,86 @@ import com.comphenix.protocol.error.ReportType;
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.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.WrappedGameProfile;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.util.AttributeKey;
import org.bukkit.Server;
import org.bukkit.entity.Player;
public class NettyChannelInjector implements Injector {
private static final String INBOUND_INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String INBOUND_PROTOCOL_GETTER_NAME = "protocol_lib_inbound_protocol_getter";
// an accessor used when we're unable to retrieve the actual packet field in an outbound packet send
private static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
private static final String INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String PROTOCOL_READER_NAME = "protocol_lib_protocol_reader";
private static final String WIRE_PACKET_ENCODER_NAME = "protocol_lib_wire_packet_encoder";
// all registered channel handlers to easier make sure we unregister them all from the pipeline
private static final String[] NETTY_HANDLER_NAMES = new String[]{
WIRE_PACKET_ENCODER_NAME, INBOUND_INTERCEPTOR_NAME, INBOUND_PROTOCOL_GETTER_NAME
private static final String[] PROTOCOL_LIB_HANDLERS = new String[]{
WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME, PROTOCOL_READER_NAME
};
private static final ReportType REPORT_CANNOT_SEND_PACKET = new ReportType("Unable to send packet %s to %s");
private static final ReportType REPORT_CANNOT_SEND_WRITE_PACKET = new ReportType("Unable to send wire packet %s to %s");
private static final ReportType REPORT_CANNOT_READ_PACKET = new ReportType("Unable to read packet %s for %s");
private static final ReportType REPORT_CANNOT_DISCONNECT = new ReportType("Unable to disconnect %s for %s");
private static final WirePacketEncoder WIRE_PACKET_ENCODER = new WirePacketEncoder();
private static final Map<Class<?>, FieldAccessor> PACKET_ACCESSORS = new ConcurrentHashMap<>(16, 0.9f);
// use random attribute name because they need to be unique and would throw on reload
private static final Class<?> LOGIN_PACKET_START_CLASS = PacketType.Login.Client.START.getPacketClass();
private static final Class<?> PACKET_PROTOCOL_CLASS = PacketType.Handshake.Client.SET_PROTOCOL.getPacketClass();
private static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
private static final AttributeKey<NettyChannelInjector> INJECTOR = AttributeKey.valueOf(getRandomKey());
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
// lazy initialized fields, if we don't need them we don't bother about them
private static FieldAccessor LOGIN_PROFILE_ACCESSOR;
private static FieldAccessor PROTOCOL_VERSION_ACCESSOR;
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
// bukkit stuff
private final Server server;
// 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 Channel wrappedChannel;
private final ChannelListener channelListener;
private final InjectionFactory injectionFactory;
private final FieldAccessor channelField;
@@ -94,44 +110,47 @@ public class NettyChannelInjector implements Injector {
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
private final AtomicBoolean closed = new AtomicBoolean(false);
private volatile boolean closed = false;
private volatile boolean injected = false;
// information about the player belonging to this injector
private String playerName;
private Player player;
private Player resolvedPlayer;
// lazy initialized fields, if we don't need them we don't bother about them
private volatile InboundProtocolReader inboundProtocolReader;
private Object playerConnection;
private InboundProtocolReader inboundProtocolReader;
public NettyChannelInjector(
Player player,
Object networkManager,
Server server,
Object netManager,
Channel channel,
ListenerManager listenerManager,
ChannelListener listener,
InjectionFactory injector,
ErrorReporter errorReporter
) {
// bukkit stuff
this.player = player;
this.server = server;
this.resolvedPlayer = player;
// protocol lib stuff
this.errorReporter = errorReporter;
this.networkProcessor = new NetworkProcessor(errorReporter);
this.listenerInvoker = new PacketListenerInvoker(networkManager);
// references
this.networkManager = networkManager;
this.channel = channel;
this.listenerManager = listenerManager;
this.networkManager = netManager;
this.wrappedChannel = channel;
this.channelListener = listener;
this.injectionFactory = injector;
// register us into the channel
this.channel.attr(INJECTOR).set(this);
this.wrappedChannel.attr(INJECTOR).set(this);
// read the channel field from the network manager given to this method
// we re-read this field every time as plugins/spigot forks might give us different network manager types
Field channelField = FuzzyReflection.fromObject(networkManager, true).getField(FuzzyFieldContract.newBuilder()
Field channelField = FuzzyReflection.fromObject(netManager, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(Channel.class)
.banModifier(Modifier.STATIC)
.build());
@@ -140,110 +159,135 @@ public class NettyChannelInjector implements Injector {
// hook here into the close future to be 100% sure that this injector gets closed when the channel we wrap gets closed
// normally we listen to the disconnect event, but there is a very small period of time, between the login and actual
// join that is not covered by the disconnect event and may lead to unexpected injector states...
this.channel.closeFuture().addListener(future -> this.close());
this.wrappedChannel.closeFuture().addListener(future -> this.close());
}
@Override
public SocketAddress getAddress() {
return this.channel.remoteAddress();
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
static Object findChannelHandler(Channel channel, Class<?> type) {
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (type.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
return null;
}
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
private static boolean hasProtocolLibHandler(Channel channel) {
for (String handler : PROTOCOL_LIB_HANDLERS) {
if (channel.pipeline().get(handler) != null) {
return true;
}
}
return false;
}
@Override
public int getProtocolVersion() {
Integer protocolVersion = this.channel.attr(PROTOCOL_VERSION).get();
Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get();
return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion;
}
@Override
public void inject() {
// ensure we are in the channel's event loop to be "thread-safe" within netty
// and our own code
if (!this.channel.eventLoop().inEventLoop()) {
channel.eventLoop().execute(this::inject);
return;
public boolean inject() {
// we only do this on the channel event loop to prevent blocking the main server thread
// and to be sure that the netty pipeline view we get is up-to-date
if (this.wrappedChannel.eventLoop().inEventLoop()) {
// ensure that we should actually inject into the channel
if (this.closed || this.wrappedChannel instanceof ByteBuddyGenerated || !this.wrappedChannel.isActive()) {
return false;
}
// check here if we need to rewrite the channel field and do so
// minecraft overrides the channel field when the channel actually becomes active, so we need to ensure that our
// proxied channel is always on that field - therefore this rewrite is event before we check if we're already
// injected into the channel
this.rewriteChannelField();
// check if we already injected into the channel
if (hasProtocolLibHandler(this.wrappedChannel)) {
return false;
}
ChannelPipeline pipeline = this.wrappedChannel.pipeline();
// since 1.20.5 the encoder is renamed to outbound_config when the channel is
// waiting for the next protocol phase
String encoderName = pipeline.get("outbound_config") != null
? "outbound_config" : "encoder";
// inject our handlers
pipeline.addAfter(
encoderName,
WIRE_PACKET_ENCODER_NAME,
WIRE_PACKET_ENCODER);
// since 1.20.2 the packet decoder will remove or reconfigure the protocol for
// terminal packets which is why we need to read it before the packet gets
// decoded
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
this.inboundProtocolReader = new InboundProtocolReader(this);
pipeline.addBefore(
"decoder",
PROTOCOL_READER_NAME,
this.inboundProtocolReader);
}
// inject inbound packet interceptor
pipeline.addAfter(
"decoder",
INTERCEPTOR_NAME,
new InboundPacketInterceptor(this, this.channelListener));
this.injected = true;
return true;
} else {
// re-run in event loop, return false as we cannot be sure if the injection actually worked
this.ensureInEventLoop(this::inject);
return false;
}
// don't need to inject if injector or channel got closed
if (this.closed.get() || !this.channel.isActive()) {
return;
}
// wrap channel inside the NetworkMananger to proxy write calls to our
// processOutbound method, this way we try to minimize the amount of thread
// jumps from event loop to minecraft main thread in order to process packets in
// main thread
Object networkManangerChannel = this.channelField.get(this.networkManager);
if (!(networkManangerChannel instanceof NettyChannelProxy)) {
EventLoop proxyEventLoop = new NettyEventLoopProxy(this.channel.eventLoop(), this);
Channel proxyChannel = new NettyChannelProxy(this.channel, proxyEventLoop, this);
this.channelField.set(this.networkManager, proxyChannel);
}
ChannelPipeline pipeline = this.channel.pipeline();
// since 1.20.5 the en-/decoder is renamed to out-/inbound_config when the
// channel is waiting for the next protocol phase (after terminal packet)
String encoderName = pipeline.get("outbound_config") != null
? "outbound_config"
: "encoder";
String decoderName = pipeline.get("inbound_config") != null
? "inbound_config"
: "decoder";
// try to add wire packet encoder
if (pipeline.context(WIRE_PACKET_ENCODER_NAME) == null) {
pipeline.addAfter(encoderName, WIRE_PACKET_ENCODER_NAME, WIRE_PACKET_ENCODER);
}
// try to add protocol reader, this is necessary because the en-/decoder will
// remove or reconfigure the protocol for terminal since 1.20.2+
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove() && pipeline.context(INBOUND_PROTOCOL_GETTER_NAME) == null) {
this.inboundProtocolReader = new InboundProtocolReader(this);
pipeline.addBefore(decoderName, INBOUND_PROTOCOL_GETTER_NAME, this.inboundProtocolReader);
}
// try to add inbound packet interceptor
if (pipeline.context(INBOUND_INTERCEPTOR_NAME) == null) {
pipeline.addAfter(decoderName, INBOUND_INTERCEPTOR_NAME, new InboundPacketInterceptor(this));
}
// mark injector as injected
this.injected = true;
}
private void uninject() {
// ensure we are in the channel's event loop to be "thread-safe" within netty
// and our own code
if (!this.channel.eventLoop().inEventLoop()) {
channel.eventLoop().execute(this::uninject);
return;
}
@Override
public void uninject() {
// ensure that we injected into the channel before trying to remove anything from it
if (this.injected) {
// uninject on the event loop to ensure the instant visibility of the change and prevent blocks of other threads
if (this.wrappedChannel.eventLoop().inEventLoop()) {
this.injected = false;
// replace wrapped channel in NetworkManager with original channel
this.channelField.set(this.networkManager, this.channel);
// remove known references to us
this.wrappedChannel.attr(INJECTOR).remove();
this.channelField.set(this.networkManager, this.wrappedChannel);
// remove all ProtocolLib netty handler
ChannelPipeline pipeline = this.channel.pipeline();
for (String handlerName : NETTY_HANDLER_NAMES) {
if (pipeline.context(handlerName) != null) {
pipeline.remove(handlerName);
for (String handler : PROTOCOL_LIB_HANDLERS) {
try {
this.wrappedChannel.pipeline().remove(handler);
} catch (NoSuchElementException ignored) {
// ignore that one, probably an edge case
}
}
} else {
this.ensureInEventLoop(this::uninject);
}
}
// mark injector as uninjected
this.injected = false;
}
@Override
public void close() {
// ensure that the injector wasn't close before
if (this.closed.compareAndSet(false, true)) {
if (!this.closed) {
this.closed = true;
// remove all of our references from the channel
this.uninject();
// remove any outgoing references
this.channel.attr(INJECTOR).remove();
// cleanup
this.savedMarkers.clear();
this.skippedPackets.clear();
@@ -254,9 +298,9 @@ public class NettyChannelInjector implements Injector {
}
@Override
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) {
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
// do not send the packet if this injector was already closed / is not injected yet
if (this.closed || !this.injected) {
return;
}
@@ -266,12 +310,19 @@ public class NettyChannelInjector implements Injector {
}
// save the given packet marker and send the packet
if (marker != null) {
this.savedMarkers.put(packet, marker);
}
this.saveMarker(packet, marker);
try {
this.listenerInvoker.send(packet);
if (this.resolvedPlayer instanceof ByteBuddyGenerated) {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
} else {
// ensure that the player is properly connected before sending
Object playerConnection = this.getPlayerConnection();
if (playerConnection != null) {
MinecraftMethods.getSendPacketMethod().invoke(playerConnection, packet);
} else {
MinecraftMethods.getNetworkManagerHandleMethod().invoke(this.networkManager, packet);
}
}
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET)
.messageParam(packet, this.playerName)
@@ -281,99 +332,89 @@ public class NettyChannelInjector implements Injector {
}
@Override
public void readServerboundPacket(Object packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
public void receiveClientPacket(Object packet) {
// do not do that if we're not injected or this injector was closed
if (this.closed || !this.injected) {
return;
}
this.ensureInEventLoop(() -> {
Runnable receiveAction = () -> {
try {
// try to invoke the method, this should normally not fail
this.listenerInvoker.read(packet);
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
// 99% the user gave wrong information to the server
this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception);
}
});
};
// execute the action on the event loop rather than any thread which we should potentially not block
if (this.wrappedChannel.eventLoop().inEventLoop()) {
receiveAction.run();
} else {
this.ensureInEventLoop(receiveAction);
}
}
@Override
public void sendWirePacket(WirePacket packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
this.ensureInEventLoop(() -> {
try {
this.channel.writeAndFlush(packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_WRITE_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
});
}
@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());
public PacketType.Protocol getInboundProtocol() {
if (this.inboundProtocolReader != null) {
return this.inboundProtocolReader.getProtocol();
}
return getCurrentProtocol(PacketType.Sender.CLIENT);
}
@Override
public Protocol getCurrentProtocol(PacketType.Sender sender) {
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.channel, sender);
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.wrappedChannel, sender);
}
@Override
public NetworkMarker getMarker(Object packet) {
return this.savedMarkers.get(packet);
}
@Override
public void saveMarker(Object packet, NetworkMarker marker) {
if (marker != null && !this.closed) {
this.savedMarkers.put(packet, marker);
}
}
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.player != null) {
return this.player;
if (this.resolvedPlayer != null) {
return this.resolvedPlayer;
}
// 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);
}
this.resolvedPlayer = this.server.getPlayerExact(this.playerName);
}
// either we resolved it or we didn't...
return this.player;
return this.resolvedPlayer;
}
@Override
public void setPlayer(Player player) {
this.injectionFactory.invalidate(this.player, this.playerName);
this.player = player;
this.resolvedPlayer = player;
this.playerName = player.getName();
this.injectionFactory.cacheInjector(player, this);
this.injectionFactory.cacheInjector(player.getName(), this);
}
@Override
public boolean isConnected() {
return this.channel.isActive();
public void disconnect(String message) {
// we're still during pre-login, just close the connection
if (this.playerConnection == null || this.resolvedPlayer instanceof ByteBuddyGenerated) {
this.wrappedChannel.disconnect();
} else {
try {
// try to call the disconnect method on the player
MinecraftMethods.getDisconnectMethod(this.playerConnection.getClass()).invoke(this.playerConnection, message);
} catch (Exception exception) {
throw new IllegalArgumentException("Unable to disconnect the current injector", exception);
}
}
}
@Override
@@ -383,97 +424,139 @@ public class NettyChannelInjector implements Injector {
@Override
public boolean isClosed() {
return this.closed.get();
return this.closed;
}
PacketType.Protocol getInboundProtocol() {
if (this.inboundProtocolReader != null) {
return this.inboundProtocolReader.getProtocol();
}
return getCurrentProtocol(PacketType.Sender.CLIENT);
}
/**
* Returns true if any plugin registed listeners or built-in listeners for the
* given packet type exist. See {@link #processInbound(ChannelHandlerContext, PacketContainer)}
*
* @param type the packet type
* @return true if the packet type has listeners; otherwise false
*/
boolean hasInboundListener(PacketType type) {
// always return true for types of the built-in listener (see #processInbound)
return type == PacketType.Handshake.Client.SET_PROTOCOL ||
type == PacketType.Login.Client.START ||
// check for plugin registed listener
this.listenerManager.hasInboundListener(type);
}
void processInbound(ChannelHandlerContext ctx, PacketContainer packet) {
// process set protocol packets for the protocol version
if (packet.getType() == PacketType.Handshake.Client.SET_PROTOCOL) {
Integer protocolVersion = packet.getIntegers().readSafely(0);
if (protocolVersion != null) {
this.channel.attr(PROTOCOL_VERSION).set(protocolVersion);
}
}
// process login packets for the player name
if (packet.getType() == PacketType.Login.Client.START) {
String username;
void tryProcessLogin(Object packet) {
// check if the given packet is a login packet
if (LOGIN_PACKET_START_CLASS != null && LOGIN_PACKET_START_CLASS.equals(packet.getClass())) {
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
// 1.19 replaced the gameprofile with username and uuid field
username = packet.getStrings().readSafely(0);
} else {
WrappedGameProfile profile = packet.getGameProfiles().readSafely(0);
username = profile != null ? profile.getName() : null;
}
// 1.19 removed the profile from the packet and now sends the plain username directly
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(LOGIN_PACKET_START_CLASS, String.class, true);
}
if (username != null) {
// get the username from the packet
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
// cache the injector and the player name
this.playerName = username;
this.injectionFactory.cacheInjector(username, this);
} else {
// ensure that the game profile accessor is available
if (LOGIN_PROFILE_ACCESSOR == null) {
LOGIN_PROFILE_ACCESSOR = Accessors.getFieldAccessor(
LOGIN_PACKET_START_CLASS,
MinecraftReflection.getGameProfileClass(),
true);
}
// the client only sends the name but the server wraps it into a GameProfile, so here we are
WrappedGameProfile profile = WrappedGameProfile.fromHandle(LOGIN_PROFILE_ACCESSOR.get(packet));
// cache the injector and the player name
this.playerName = profile.getName();
this.injectionFactory.cacheInjector(profile.getName(), this);
}
}
// invoke plugin listeners
if (this.listenerManager.hasInboundListener(packet.getType())) {
this.processInboundInternal(ctx, packet);
} else {
ctx.fireChannelRead(packet.getHandle());
}
}
private void processInboundInternal(ChannelHandlerContext ctx, PacketContainer packetContainer) {
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;
}
// create event and invoke listeners
PacketEvent event = PacketEvent.fromClient(this, packetContainer, this.player);
this.listenerManager.invokeInboundPacketListeners(event);
// protocol version begin
if (PACKET_PROTOCOL_CLASS != null && PACKET_PROTOCOL_CLASS.equals(packet.getClass())) {
// ensure the protocol version accessor is available
if (PROTOCOL_VERSION_ACCESSOR == null) {
try {
Field ver = FuzzyReflection.fromClass(PACKET_PROTOCOL_CLASS, true).getField(FuzzyFieldContract.newBuilder()
.banModifier(Modifier.STATIC)
.typeExact(int.class)
.build());
PROTOCOL_VERSION_ACCESSOR = Accessors.getFieldAccessor(ver);
} catch (IllegalArgumentException exception) {
// unable to resolve that field, continue no-op
PROTOCOL_VERSION_ACCESSOR = NO_OP_ACCESSOR;
}
}
// get packet of event
Object packet = event.getPacket().getHandle();
// read the protocol version from the field if available
if (PROTOCOL_VERSION_ACCESSOR != NO_OP_ACCESSOR) {
int protocolVersion = (int) PROTOCOL_VERSION_ACCESSOR.get(packet);
this.wrappedChannel.attr(PROTOCOL_VERSION).set(protocolVersion);
}
}
}
// fire the intercepted packet down the pipeline if it wasn't cancelled and isn't null
if (!event.isCancelled() && packet != null) {
private void rewriteChannelField() {
// check if we need to rewrite the channel or if the channel is already correct (prevent wrapping a wrapped channel)
Object currentChannel = this.channelField.get(this.networkManager);
if (currentChannel instanceof NettyChannelProxy) {
return;
}
// the field is not correct, rewrite now to our handler
Channel ch = new NettyChannelProxy(this.wrappedChannel, new NettyEventLoopProxy(this.wrappedChannel.eventLoop(), this) {
@Override
protected Runnable doProxyRunnable(Runnable original) {
return NettyChannelInjector.this.processOutbound(original);
}
@Override
protected <T> Callable<T> doProxyCallable(Callable<T> original) {
return NettyChannelInjector.this.processOutbound(original);
}
}, this);
this.channelField.set(this.networkManager, ch);
}
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.wrappedChannel.eventLoop(), runnable);
}
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
}
void processInboundPacket(ChannelHandlerContext ctx, Object packet, PacketType packetType) {
if (this.channelListener.hasMainThreadListener(packetType) && !this.server.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(
() -> this.processInboundPacket(ctx, packet, packetType));
return;
}
// call packet handlers, a null result indicates that we shouldn't change anything
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent interceptionResult = this.channelListener.onPacketReceiving(this, packetContainer, null);
if (interceptionResult == null) {
this.ensureInEventLoop(ctx.channel().eventLoop(), () -> ctx.fireChannelRead(packet));
return;
}
// fire the intercepted packet down the pipeline if it wasn't cancelled
if (!interceptionResult.isCancelled()) {
this.ensureInEventLoop(
ctx.channel().eventLoop(),
() -> ctx.fireChannelRead(interceptionResult.getPacket().getHandle()));
// check if there were any post events added the packet after we fired it down the pipeline
// we use this way as we don't want to construct a new network manager accidentally
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
NetworkMarker marker = NetworkMarker.getNetworkMarker(interceptionResult);
if (marker != null) {
this.networkProcessor.invokePostEvent(event, marker);
this.networkProcessor.invokePostEvent(interceptionResult, marker);
}
}
}
<T> T processOutbound(T action) {
// try to get packet field accessor or return on failure
// get the accessor to the packet field
// if we are unable to look up the accessor then just return the runnable, probably nothing of our business
FieldAccessor packetAccessor = this.lookupPacketAccessor(action);
if (packetAccessor == FieldAccessor.NO_OP_ACCESSOR) {
if (packetAccessor == NO_OP_ACCESSOR) {
return action;
}
@@ -500,7 +583,7 @@ public class NettyChannelInjector implements Injector {
PacketType.Protocol protocol = this.getCurrentProtocol(PacketType.Sender.SERVER);
if (protocol == Protocol.UNKNOWN) {
ProtocolLogger.debug("skipping unknown outbound protocol for {0}", packet.getClass());
ProtocolLogger.debug("skipping unknown protocol for {0}", packet.getClass());
return action;
}
@@ -511,28 +594,30 @@ public class NettyChannelInjector implements Injector {
}
// no listener and no marker - no magic :)
if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
if (!this.channelListener.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
// ensure that we are on the main thread if we need to
if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) {
if (this.channelListener.hasMainThreadListener(packetType) && !this.server.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, null, true));
ProtocolLibrary.getScheduler().runTask(
() -> this.sendServerPacket(packet, null, true));
return null;
}
// create event and invoke listeners
// 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
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
this.listenerManager.invokeOutboundPacketListeners(event);
// get packet of event
Object interceptedPacket = event.getPacket().getHandle();
PacketEvent event = this.channelListener.onPacketSending(this, packetContainer, marker);
if (event == null) {
return action;
}
// if the event wasn't cancelled by this action we must recheck if the packet changed during the method call
if (!event.isCancelled() && interceptedPacket != null) {
if (!event.isCancelled()) {
// rewrite the packet in the given action if the packet was changed during the event call
Object interceptedPacket = event.getPacket().getHandle();
if (interceptedPacket != packet) {
packetAccessor.set(action, interceptedPacket);
}
@@ -582,29 +667,37 @@ public class NettyChannelInjector implements Injector {
}
private FieldAccessor lookupPacketAccessor(Object action) {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), key -> {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
try {
Field packetField = FuzzyReflection.fromClass(key, true).getField(
FuzzyFieldContract.newBuilder()
// find the field
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
.newBuilder()
.typeSuperOf(MinecraftReflection.getPacketClass())
.build());
return Accessors.getFieldAccessor(packetField);
} catch (IllegalArgumentException exception) {
// return noop accessor because computeIfAbsent interprets null as a missing key
return FieldAccessor.NO_OP_ACCESSOR;
// no such field found :(
return NO_OP_ACCESSOR;
}
});
}
private void ensureInEventLoop(Runnable runnable) {
this.ensureInEventLoop(this.channel.eventLoop(), runnable);
private Object getPlayerConnection() {
// resolve the player connection if needed
if (this.playerConnection == null) {
Player target = this.getPlayer();
if (target == null) {
return null;
}
this.playerConnection = MinecraftFields.getPlayerConnection(target);
}
// cannot be null at this point
return this.playerConnection;
}
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
public Channel getWrappedChannel() {
return this.wrappedChannel;
}
}
@@ -0,0 +1,44 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.temporary.MinimalInjector;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
final class NettyChannelMinimalInjector implements MinimalInjector {
private final NettyChannelInjector injector;
public NettyChannelMinimalInjector(NettyChannelInjector injector) {
this.injector = injector;
}
@Override
public SocketAddress getAddress() {
return this.injector.getWrappedChannel().remoteAddress();
}
@Override
public void disconnect(String message) {
this.injector.disconnect(message);
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
this.injector.sendServerPacket(packet, marker, filtered);
}
@Override
public Player getPlayer() {
return this.injector.getPlayer();
}
@Override
public boolean isConnected() {
return this.injector.getWrappedChannel().isActive();
}
public NettyChannelInjector getInjector() {
return this.injector;
}
}
@@ -1,15 +1,5 @@
package com.comphenix.protocol.injector.netty.channel;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelPromise;
@@ -20,12 +10,21 @@ import io.netty.util.concurrent.Future;
import io.netty.util.concurrent.ProgressivePromise;
import io.netty.util.concurrent.Promise;
import io.netty.util.concurrent.ScheduledFuture;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Spliterator;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Consumer;
/**
* An abstract event loop implementation which delegates all calls to a given event loop, but proxies all calls which
* schedule something on the event loop to methods which decide what should happen to the scheduled task.
*/
final class NettyEventLoopProxy implements EventLoop {
abstract class NettyEventLoopProxy implements EventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -41,7 +40,7 @@ final class NettyEventLoopProxy implements EventLoop {
private Runnable proxyRunnable(Runnable original) {
// execute the proxy and check if we need to do anything
Runnable proxied = this.injector.processOutbound(original);
Runnable proxied = this.doProxyRunnable(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -56,7 +55,7 @@ final class NettyEventLoopProxy implements EventLoop {
private <T> Callable<T> proxyCallable(Callable<T> original) {
// execute the proxy and check if we need to do anything
Callable<T> proxied = this.injector.processOutbound(original);
Callable<T> proxied = this.doProxyCallable(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -69,6 +68,26 @@ final class NettyEventLoopProxy implements EventLoop {
}
}
/**
* Proxies the given runnable. The returned runnable will be executed instead of the original. If this method returns
* null a no-op runnable will be scheduled instead, preventing the original action from happening.
*
* @param original the runnable to proxy.
* @return the runnable to execute instead, null to execute no action.
*/
protected abstract Runnable doProxyRunnable(Runnable original);
/**
* Proxies the given callable. The returned callable will be executed instead of the original. If this method returns
* null a callable which always returns null will be scheduled instead, preventing the original action from
* happening.
*
* @param original the callable to proxy.
* @param <T> the return type of the original callable.
* @return the callable to execute instead of the original, null to use a no-op callable instead.
*/
protected abstract <T> Callable<T> doProxyCallable(Callable<T> original);
@Override
public EventLoopGroup parent() {
return this.delegate.parent();
@@ -1,237 +0,0 @@
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 <code>send</code>, <code>read</code>, and <code>disconnect</code>
* 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.
*
* <p>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.
*
* <p>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<Method> 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<Method> 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<Object> 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.
*
* <p>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);
}
}
}
@@ -1,10 +1,9 @@
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;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
@@ -12,15 +11,18 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private final ErrorReporter errorReporter;
private final InjectionFactory injectionFactory;
private final InjectionFactory factory;
private final NetworkManagerInjector listener;
private final TemporaryPlayerFactory playerFactory;
public InjectionChannelInboundHandler(
ErrorReporter errorReporter,
InjectionFactory injectionFactory
InjectionFactory factory,
NetworkManagerInjector listener,
TemporaryPlayerFactory playerFactory
) {
this.errorReporter = errorReporter;
this.injectionFactory = injectionFactory;
this.factory = factory;
this.listener = listener;
this.playerFactory = playerFactory;
}
@Override
@@ -33,11 +35,11 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
// 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.injectionFactory.isClosed()) {
if (!this.factory.isClosed()) {
try {
this.injectionFactory.fromChannel(ctx.channel()).inject();
this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject();
} catch (Exception exception) {
this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
.messageParam(ctx.channel())
.error(exception)
.build());
@@ -50,7 +52,7 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overhead
// we do it this way to prevent the lookup overheat
return true;
}
}
@@ -28,7 +28,7 @@ final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter {
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overhead
// we do it this way to prevent the lookup overheat
return true;
}
}
@@ -7,14 +7,21 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.Server;
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.injector.ListenerManager;
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.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -25,36 +32,105 @@ import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.ChannelFuture;
public class NetworkManagerInjector {
public class NetworkManagerInjector implements ChannelListener {
private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject";
private static final TemporaryPlayerFactory PLAYER_FACTORY = new TemporaryPlayerFactory();
// all list fields which we've overridden and need to revert to a non-proxying list afterwards
private final Set<Pair<Object, FieldAccessor>> overriddenLists = new HashSet<>();
private final ErrorReporter errorReporter;
private final ListenerInvoker listenerInvoker;
private final InjectionFactory injectionFactory;
// netty handler
// injectors based on this "global" injector
private final PlayerInjectionHandler playerInjectionHandler;
private final InjectionChannelInitializer pipelineInjectorHandler;
private boolean debug = false;
// status of this injector
private boolean closed = false;
private boolean injected = false;
public NetworkManagerInjector(Plugin plugin, ListenerManager listenerManager, ErrorReporter reporter) {
public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker listenerInvoker, ErrorReporter reporter) {
this.errorReporter = reporter;
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
this.listenerInvoker = listenerInvoker;
this.injectionFactory = new InjectionFactory(plugin, server, reporter);
// hooking netty handlers
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
this.errorReporter,
this.injectionFactory);
this.injectionFactory,
this,
PLAYER_FACTORY);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
// other injectors
this.playerInjectionHandler = new NetworkManagerPlayerInjector(
this,
this.injectionFactory);
}
public Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
@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;
}
@SuppressWarnings("unchecked")
@@ -155,4 +231,8 @@ public class NetworkManagerInjector {
this.overriddenLists.clear();
this.injectionFactory.close();
}
public PlayerInjectionHandler getPlayerInjectionHandler() {
return this.playerInjectionHandler;
}
}
@@ -0,0 +1,68 @@
package com.comphenix.protocol.injector.netty.manager;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.injector.netty.channel.NettyChannelInjector;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import io.netty.channel.Channel;
final class NetworkManagerPlayerInjector implements PlayerInjectionHandler {
private final ChannelListener listener;
private final InjectionFactory injectionFactory;
public NetworkManagerPlayerInjector(
ChannelListener listener,
InjectionFactory injectionFactory
) {
this.listener = listener;
this.injectionFactory = injectionFactory;
}
@Override
public int getProtocolVersion(Player player) {
return this.injectionFactory.fromPlayer(player, this.listener).getProtocolVersion();
}
@Override
public void injectPlayer(Player player, ConflictStrategy strategy) {
this.injectionFactory.fromPlayer(player, this.listener).inject();
}
@Override
public boolean uninjectPlayer(Player player) {
this.injectionFactory.fromPlayer(player, this.listener).uninject();
return true;
}
@Override
public void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters) {
this.injectionFactory.fromPlayer(receiver, this.listener).sendServerPacket(packet.getHandle(), marker, filters);
}
@Override
public void receiveClientPacket(Player player, Object mcPacket) {
this.injectionFactory.fromPlayer(player, this.listener).receiveClientPacket(mcPacket);
}
@Override
public void updatePlayer(Player player) {
this.injectionFactory.fromPlayer(player, this.listener).inject();
}
@Override
public Channel getChannel(Player player) {
Injector injector = this.injectionFactory.fromPlayer(player, this.listener);
if (injector instanceof NettyChannelInjector) {
return ((NettyChannelInjector) injector).getWrappedChannel();
}
return null;
}
}
@@ -0,0 +1,81 @@
package com.comphenix.protocol.injector.player;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import io.netty.channel.Channel;
public interface PlayerInjectionHandler {
/**
* Retrieve the protocol version of the given player.
*
* @param player - the player.
* @return The protocol version, or {@link Integer#MIN_VALUE}.
*/
int getProtocolVersion(Player player);
/**
* Initialize a player hook, allowing us to read server packets.
* <p>
* This call will be ignored if there's no listener that can receive the given events.
*
* @param player - player to hook.
* @param strategy - how to handle injection conflicts.
*/
void injectPlayer(Player player, ConflictStrategy strategy);
/**
* Uninject the given player.
*
* @param player - player to uninject.
* @return TRUE if a player has been uninjected, FALSE otherwise.
*/
boolean uninjectPlayer(Player player);
/**
* Send the given packet to the given receiver.
*
* @param receiver - the player receiver.
* @param packet - the packet to send.
* @param marker - network marker.
* @param filters - whether or not to invoke the packet filters.
*/
void sendServerPacket(Player receiver, PacketContainer packet, NetworkMarker marker, boolean filters);
/**
* Process a packet as if it were sent by the given player.
*
* @param player - the sender.
* @param mcPacket - the packet to process.
*/
void receiveClientPacket(Player player, Object mcPacket);
/**
* Ensure that packet readers are informed of this player reference.
*
* @param player - the player to update.
*/
void updatePlayer(Player player);
Channel getChannel(Player player);
/**
* How to handle a previously existing player injection.
*
* @author Kristian
*/
enum ConflictStrategy {
/**
* Override it.
*/
OVERRIDE,
/**
* Immediately exit.
*/
BAIL_OUT;
}
}
@@ -0,0 +1,50 @@
package com.comphenix.protocol.injector.temporary;
import com.comphenix.protocol.events.NetworkMarker;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
/**
* Represents an injector that only gives access to a player's socket.
*
* @author Kristian
*/
public interface MinimalInjector {
/**
* Retrieve the associated address of this player.
*
* @return The associated address.
*/
SocketAddress getAddress();
/**
* Attempt to disconnect the current client.
*
* @param message - the message to display.
*/
void disconnect(String message);
/**
* Send a packet to the client.
*
* @param packet - server packet to send.
* @param marker - the network marker.
* @param filtered - whether or not the packet will be filtered by our listeners.
*/
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
/**
* Retrieve the hooked player.
*
* @return The hooked player.
*/
Player getPlayer();
/**
* Determines if the player is currently connected.
*
* @return true if the player is connected.
*/
boolean isConnected();
}
@@ -1,9 +1,5 @@
package com.comphenix.protocol.injector.temporary;
import java.util.Objects;
import com.comphenix.protocol.injector.netty.Injector;
/**
* A temporary player created by ProtocolLib when a true player instance does not exist.
* <p>
@@ -12,17 +8,15 @@ import com.comphenix.protocol.injector.netty.Injector;
*/
public class TemporaryPlayer {
protected volatile Injector injector;
private volatile MinimalInjector injector;
public Injector getInjector() {
MinimalInjector getInjector() {
return this.injector;
}
void setInjector(Injector injector) {
Objects.requireNonNull(injector, "injector can't be null");
if (this.injector != null) {
throw new IllegalStateException("Can't redefine injector for temporary player");
void setInjector(MinimalInjector injector) {
if (injector == null) {
throw new IllegalArgumentException("Injector cannot be NULL.");
}
this.injector = injector;
@@ -17,20 +17,17 @@
package com.comphenix.protocol.injector.temporary;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
import net.bytebuddy.dynamic.scaffold.subclass.ConstructorStrategy;
import net.bytebuddy.implementation.FieldAccessor;
import net.bytebuddy.implementation.MethodCall;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.FieldValue;
@@ -39,6 +36,8 @@ import net.bytebuddy.implementation.bind.annotation.RuntimeType;
import net.bytebuddy.implementation.bind.annotation.This;
import net.bytebuddy.matcher.ElementMatcher;
import net.bytebuddy.matcher.ElementMatchers;
import org.bukkit.Server;
import org.bukkit.entity.Player;
/**
* Create fake player instances that represents pre-authenticated clients.
@@ -47,15 +46,13 @@ public class TemporaryPlayerFactory {
private static final Constructor<? extends Player> PLAYER_CONSTRUCTOR = setupProxyPlayerConstructor();
private TemporaryPlayerFactory() {}
/**
* Retrieve the injector from a given player if it contains one.
*
* @param player - the player that may contain a reference to a player injector.
* @return The referenced player injector, or NULL if none can be found.
*/
public static Injector getInjectorFromPlayer(Player player) {
public static MinimalInjector getInjectorFromPlayer(Player player) {
if (player instanceof TemporaryPlayer) {
return ((TemporaryPlayer) player).getInjector();
}
@@ -68,7 +65,7 @@ public class TemporaryPlayerFactory {
* @param player - the player to update.
* @param injector - the injector to store.
*/
public static void setInjectorForPlayer(Player player, Injector injector) {
public static void setInjectorInPlayer(Player player, MinimalInjector injector) {
((TemporaryPlayer) player).setInjector(injector);
}
@@ -79,10 +76,11 @@ public class TemporaryPlayerFactory {
public Object delegate(
@This Object obj,
@Origin Method method,
@FieldValue("injector") Injector injector,
@FieldValue("server") Server server,
@AllArguments Object... args
) throws Throwable {
String methodName = method.getName();
MinimalInjector injector = ((TemporaryPlayer) obj).getInjector();
if (injector == null) {
throw new IllegalStateException("Unable to find injector.");
@@ -94,7 +92,7 @@ public class TemporaryPlayerFactory {
} else if (methodName.equals("getAddress")) {
return injector.getAddress();
} else if (methodName.equals("getServer")) {
return Bukkit.getServer();
return server;
}
// Handle send message methods
@@ -146,15 +144,21 @@ public class TemporaryPlayerFactory {
try {
final Constructor<?> constructor = ByteBuddyFactory.getInstance()
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.name(TemporaryPlayerFactory.class.getPackage().getName() + ".TemporaryPlayerInvocationHandler")
.implement(Player.class)
.defineField("server", Server.class, Visibility.PRIVATE)
.defineConstructor(Visibility.PUBLIC)
.withParameters(Server.class)
.intercept(MethodCall.invoke(TemporaryPlayer.class.getDeclaredConstructor())
.andThen(FieldAccessor.ofField("server").setsArgumentAt(0)))
.method(callbackFilter)
.intercept(implementation)
.make()
.load(ByteBuddyFactory.getInstance().getClassLoader(), ClassLoadingStrategy.Default.INJECTION)
.getLoaded()
.getDeclaredConstructor();
.getDeclaredConstructor(Server.class);
return (Constructor<? extends Player>) constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
@@ -168,9 +172,9 @@ public class TemporaryPlayerFactory {
* @param message - a message.
* @return Always NULL.
*/
private static Object sendMessage(Injector injector, String message) {
private static Object sendMessage(MinimalInjector injector, String message) {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendClientboundPacket(packet.getHandle(), null, false);
injector.sendServerPacket(packet.getHandle(), null, false);
}
return null;
@@ -193,13 +197,27 @@ public class TemporaryPlayerFactory {
* Note that a temporary player has not yet been assigned a name, and thus cannot be
* uniquely identified. Use the address instead.
*
* @param server - the current server.
* @return A temporary player instance.
*/
public static Player createTemporaryPlayer() {
public Player createTemporaryPlayer(final Server server) {
try {
return PLAYER_CONSTRUCTOR.newInstance();
return PLAYER_CONSTRUCTOR.newInstance(server);
} catch (ReflectiveOperationException exception) {
throw new IllegalStateException("Unable to create temporary player", exception);
}
}
/**
* Construct a temporary player with the given associated socket injector.
*
* @param server - the parent server.
* @param injector - the referenced socket injector.
* @return The temporary player.
*/
public Player createTemporaryPlayer(Server server, MinimalInjector injector) {
Player temporary = this.createTemporaryPlayer(server);
((TemporaryPlayer) temporary).setInjector(injector);
return temporary;
}
}
@@ -4,21 +4,6 @@ import java.lang.reflect.Constructor;
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.
*
@@ -9,25 +9,6 @@ import java.lang.reflect.Field;
*/
public interface FieldAccessor {
/**
* NoOp Accessor, does what is says: nothing.
*/
static final FieldAccessor NO_OP_ACCESSOR = new FieldAccessor() {
@Override
public Object get(Object instance) {
return null;
}
@Override
public void set(Object instance, Object value) {
}
@Override
public Field getField() {
return null;
}
};
/**
* Retrieve the value of a field for a particular instance.
*
@@ -1,18 +1,16 @@
package com.comphenix.protocol.reflect.instances;
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;
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;
public class MinecraftGenerator {
@@ -60,20 +58,14 @@ public class MinecraftGenerator {
try {
String name = type.getName();
if (name.contains("it.unimi.dsi.fastutil")) {
// convert interface maps to OpenHashMaps
if (!name.endsWith("OpenHashMap")) {
name = name.replace("Map", "OpenHashMap");
}
Class<?> hashMapClass = Class.forName(name);
return Accessors.getConstructorAccessorOrNull(hashMapClass);
Class<?> clz = Class.forName(name.replace("Map", "OpenHashMap"));
return Accessors.getConstructorAccessorOrNull(clz);
}
} catch (Exception ignored) {
}
return ConstructorAccessor.NO_OP_ACCESSOR;
return null;
});
if (ctor != ConstructorAccessor.NO_OP_ACCESSOR) {
if (ctor != null) {
try {
return ctor.invoke();
} catch (Exception ignored) {
@@ -35,13 +35,12 @@ import net.bytebuddy.matcher.ElementMatchers;
public final class MinecraftMethods {
// For player connection
private volatile static MethodAccessor playerConnectionSendMethod;
private volatile static MethodAccessor playerConnectionDisconnectMethod;
private volatile static MethodAccessor sendPacketMethod;
private volatile static MethodAccessor disconnectMethod;
// For network manager
private volatile static MethodAccessor networkManagerSend;
private volatile static MethodAccessor networkManagerHandle;
private volatile static MethodAccessor networkManagerPacketRead;
private volatile static MethodAccessor networkManagerDisconnect;
// For packet
private volatile static MethodAccessor packetReadByteBuf;
@@ -79,18 +78,18 @@ public final class MinecraftMethods {
*
* @return The send packet method.
*/
public static MethodAccessor getPlayerConnectionSendMethod() {
if (playerConnectionSendMethod == null) {
public static MethodAccessor getSendPacketMethod() {
if (sendPacketMethod == null) {
FuzzyReflection serverHandlerClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass());
try {
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(1)
.returnTypeVoid()
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build()));
} catch (IllegalArgumentException e) {
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.nameRegex("sendPacket.*")
.returnTypeVoid()
.parameterCount(1)
@@ -98,41 +97,33 @@ public final class MinecraftMethods {
}
}
return playerConnectionSendMethod;
return sendPacketMethod;
}
/**
* Retrieve the disconnect method for a given player connection.
*
* @param playerConnection - the player connection.
* @return The
*/
public static MethodAccessor getPlayerConnectionDisconnectMethod() {
if (playerConnectionDisconnectMethod == null) {
FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass());
if (MinecraftVersion.v1_21_0.atOrAbove()) {
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
public static MethodAccessor getDisconnectMethod(Class<?> playerConnection) {
if (disconnectMethod == null) {
FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(playerConnection);
try {
disconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.parameterExactType(String.class, 0)
.build()));
} else {
try {
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(String.class, 0)
.build()));
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class);
playerConnectionDisconnectMethod = Accessors.getMethodAccessor(disconnect);
}
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class);
disconnectMethod = Accessors.getMethodAccessor(disconnect);
}
}
return playerConnectionDisconnectMethod;
return disconnectMethod;
}
/**
@@ -140,8 +131,8 @@ public final class MinecraftMethods {
*
* @return The handle method.
*/
public static MethodAccessor getNetworkManagerSendMethod() {
if (networkManagerSend == null) {
public static MethodAccessor getNetworkManagerHandleMethod() {
if (networkManagerHandle == null) {
Method handleMethod = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethod(FuzzyMethodContract.newBuilder()
@@ -150,10 +141,10 @@ public final class MinecraftMethods {
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build());
networkManagerSend = Accessors.getMethodAccessor(handleMethod);
networkManagerHandle = Accessors.getMethodAccessor(handleMethod);
}
return networkManagerSend;
return networkManagerHandle;
}
/**
@@ -172,27 +163,6 @@ public final class MinecraftMethods {
return networkManagerPacketRead;
}
/**
* Retrieve the handle/send packet method of network manager.
*
* @return The handle method.
*/
public static MethodAccessor getNetworkManagerDisconnectMethod() {
if (networkManagerDisconnect == null) {
Method handleMethod = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethod(FuzzyMethodContract.newBuilder()
.banModifier(Modifier.STATIC)
.returnTypeVoid()
.parameterCount(1)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build());
networkManagerDisconnect = Accessors.getMethodAccessor(handleMethod);
}
return networkManagerDisconnect;
}
/**
* Retrieve the Packet.read(PacketDataSerializer) method.
*
@@ -373,11 +373,6 @@ public final class MinecraftReflection {
if (clazz == null || object == null) {
return false;
}
// check for accidental class objects
if (object instanceof Class) {
return clazz.isAssignableFrom((Class<?>) object);
}
return clazz.isAssignableFrom(object.getClass());
}
@@ -507,10 +502,6 @@ public final class MinecraftReflection {
return is(getCraftItemStackClass(), obj);
}
public static boolean isIChatBaseComponent(Class<?> target) {
return is(getIChatBaseComponentClass(), target);
}
/**
* Retrieve the EntityPlayer (NMS) class.
*
@@ -86,15 +86,6 @@ public class AdventureComponentConverter {
return WrappedChatComponent.fromJson(SERIALIZER.serialize(component));
}
/**
* Converts an object that is instance of {@link Component} into a ProtocolLib wrapper
* @param component Component
* @return ProtocolLib wrapper
*/
public static WrappedChatComponent fromComponentObject(Object component) {
return fromComponent((Component) component);
}
/**
* Converts a {@link WrappedComponentStyle} into a {@link Style}
* @param wrapper ProtocolLib wrapper
@@ -11,10 +11,6 @@ import java.util.Map;
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;
@@ -27,6 +23,10 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
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,21 +623,6 @@ public abstract class EnumWrappers {
}
}
/**
* 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;
@@ -662,9 +647,7 @@ public abstract class EnumWrappers {
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 INITIALIZING = false;
private static boolean INITIALIZED = false;
private static Map<Class<?>, EquivalentConverter<?>> FROM_NATIVE = new HashMap<>();
private static Map<Class<?>, EquivalentConverter<?>> FROM_WRAPPER = new HashMap<>();
@@ -677,129 +660,116 @@ public abstract class EnumWrappers {
if (INITIALIZED)
return;
synchronized (EnumWrappers.class) {
// Recheck initialization status inside the lock
if (INITIALIZING || INITIALIZED)
return;
INITIALIZED = true;
// Prevent circular calls to initialize during initialization
// (certain methods below indirectly call initialize again)
INITIALIZING = true;
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
} else {
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
}
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
CHAT_VISIBILITY_CLASS = MinecraftReflection.getMinecraftClass("world.entity.player.EnumChatVisibility", "world.entity.player.ChatVisibility", "world.entity.player.ChatVisiblity"); // Some versions have a typo
} else {
CHAT_VISIBILITY_CLASS = getEnum(PacketType.Play.Client.SETTINGS.getPacketClass(), 0);
}
try {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
} catch (Exception ex) {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
}
try {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.SERVER_DIFFICULTY.getPacketClass(), 0);
} catch (Exception ex) {
DIFFICULTY_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 1);
}
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
}
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.getPacketClass(), 0);
}
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
RESOURCE_PACK_STATUS_CLASS = getEnum(PacketType.Play.Client.RESOURCE_PACK_STATUS.getPacketClass(), 0);
TITLE_ACTION_CLASS = getEnum(PacketType.Play.Server.TITLE.getPacketClass(), 0);
WORLD_BORDER_ACTION_CLASS = getEnum(PacketType.Play.Server.WORLD_BORDER.getPacketClass(), 0);
COMBAT_EVENT_TYPE_CLASS = getEnum(PacketType.Play.Server.COMBAT_EVENT.getPacketClass(), 0);
PLAYER_DIG_TYPE_CLASS = getEnum(PacketType.Play.Client.BLOCK_DIG.getPacketClass(), 1);
PLAYER_ACTION_CLASS = getEnum(PacketType.Play.Client.ENTITY_ACTION.getPacketClass(), 0);
SCOREBOARD_ACTION_CLASS = getEnum(PacketType.Play.Server.SCOREBOARD_SCORE.getPacketClass(), 0);
PARTICLE_CLASS = getEnum(PacketType.Play.Server.WORLD_PARTICLES.getPacketClass(), 0);
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
PLAYER_INFO_ACTION_CLASS = getEnum(PacketType.Play.Server.PLAYER_INFO.getPacketClass(), 0);
if (PLAYER_INFO_ACTION_CLASS == null) {
// todo: we can also use getField(0).getGenericType().getTypeParameters()[0]; but this should hold for now
PLAYER_INFO_ACTION_CLASS = PacketType.Play.Server.PLAYER_INFO.getPacketClass().getClasses()[1];
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.getPacketClass(), 0);
}
try {
// TODO enum names are more stable than their packet associations
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
} catch (Exception ex) {
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
}
try {
// TODO enum names are more stable than their packet associations
ITEM_SLOT_CLASS = MinecraftReflection.getMinecraftClass("world.entity.EnumItemSlot", "world.entity.EquipmentSlot", "EnumItemSlot");
} catch (Exception ex) {
ITEM_SLOT_CLASS = getEnum(PacketType.Play.Server.ENTITY_EQUIPMENT.getPacketClass(), 0);
}
// In 1.17 the hand and use action class is no longer a field in the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
// In 1.17 the hand and use action class is no longer a field in the packet
if (MinecraftVersion.CAVES_CLIFFS_1.atOrAbove()) {
HAND_CLASS = MinecraftReflection.getMinecraftClass("world.EnumHand", "world.InteractionHand");
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
}
ENTITY_USE_ACTION_CLASS = getType.getReturnType();
} else {
HAND_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 1);
ENTITY_USE_ACTION_CLASS = getEnum(PacketType.Play.Client.USE_ENTITY.getPacketClass(), 0);
}
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
} else {
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
}
// 1.19 removed the entity spawn packet and moved the direction into a seperated class
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
DIRECTION_CLASS = MinecraftReflection.getMinecraftClass("core.EnumDirection", "core.Direction");
} else {
DIRECTION_CLASS = getEnum(PacketType.Play.Server.SPAWN_ENTITY_PAINTING.getPacketClass(), 0);
}
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
CHAT_TYPE_CLASS = getEnum(PacketType.Play.Server.CHAT.getPacketClass(), 0);
ENTITY_POSE_CLASS = MinecraftReflection.getNullableNMS("world.entity.EntityPose", "world.entity.Pose", "EntityPose");
DISPLAY_SLOT_CLASS = MinecraftReflection.getNullableNMS("world.scores.DisplaySlot");
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
associate(PROTOCOL_CLASS, Protocol.class, getProtocolConverter());
associate(CLIENT_COMMAND_CLASS, ClientCommand.class, getClientCommandConverter());
associate(CHAT_VISIBILITY_CLASS, ChatVisibility.class, getChatVisibilityConverter());
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
associate(HAND_CLASS, Hand.class, getHandConverter());
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
associate(DISPLAY_SLOT_CLASS, DisplaySlot.class, getDisplaySlotConverter());
associate(RENDER_TYPE_CLASS, RenderType.class, getRenderTypeConverter());
associate(CHAT_FORMATTING_CLASS, ChatFormatting.class, getChatFormattingConverter());
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());
associate(DIFFICULTY_CLASS, Difficulty.class, getDifficultyConverter());
associate(GAMEMODE_CLASS, NativeGameMode.class, getGameModeConverter());
associate(RESOURCE_PACK_STATUS_CLASS, ResourcePackStatus.class, getResourcePackStatusConverter());
associate(PLAYER_INFO_ACTION_CLASS, PlayerInfoAction.class, getPlayerInfoActionConverter());
associate(TITLE_ACTION_CLASS, TitleAction.class, getTitleActionConverter());
associate(WORLD_BORDER_ACTION_CLASS, WorldBorderAction.class, getWorldBorderActionConverter());
associate(COMBAT_EVENT_TYPE_CLASS, CombatEventType.class, getCombatEventTypeConverter());
associate(PLAYER_DIG_TYPE_CLASS, PlayerDigType.class, getPlayerDiggingActionConverter());
associate(PLAYER_ACTION_CLASS, PlayerAction.class, getEntityActionConverter());
associate(SCOREBOARD_ACTION_CLASS, ScoreboardAction.class, getUpdateScoreActionConverter());
associate(PARTICLE_CLASS, Particle.class, getParticleConverter());
associate(SOUND_CATEGORY_CLASS, SoundCategory.class, getSoundCategoryConverter());
associate(ITEM_SLOT_CLASS, ItemSlot.class, getItemSlotConverter());
associate(DIRECTION_CLASS, Direction.class, getDirectionConverter());
associate(CHAT_TYPE_CLASS, ChatType.class, getChatTypeConverter());
associate(HAND_CLASS, Hand.class, getHandConverter());
associate(ENTITY_USE_ACTION_CLASS, EntityUseAction.class, getEntityUseActionConverter());
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());
}
INITIALIZED = true;
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
}
}
@@ -963,11 +933,6 @@ public abstract class EnumWrappers {
return CHAT_FORMATTING_CLASS;
}
public static Class<?> getClientIntentClass() {
initialize();
return CLIENT_INTENT_CLASS;
}
// Get the converters
public static EquivalentConverter<Protocol> getProtocolConverter() {
return new EnumConverter<>(getProtocolClass(), Protocol.class);
@@ -1061,10 +1026,6 @@ public abstract class EnumWrappers {
return new EnumConverter<>(getChatFormattingClass(), ChatFormatting.class);
}
public static EquivalentConverter<ClientIntent> 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)
@@ -95,8 +95,8 @@ public class WrappedTeamParameters extends AbstractWrapper {
private Builder(@Nullable WrappedTeamParameters template) {
if (template != null) {
this.displayName = template.getDisplayName();
this.prefix = template.getPrefix();
this.suffix = template.getSuffix();
this.prefix = template.getDisplayName();
this.suffix = template.getDisplayName();
this.nametagVisibility = template.getNametagVisibility();
this.collisionRule = template.getCollisionRule();
this.color = template.getColor();
@@ -1,62 +1,44 @@
package com.comphenix.protocol;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflectionTestUtil;
import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.MoreExecutors;
import net.minecraft.SharedConstants;
import net.minecraft.commands.CommandDispatcher;
import net.minecraft.core.IRegistry;
import net.minecraft.core.IRegistryCustom;
import net.minecraft.commands.Commands.CommandSelection;
import net.minecraft.core.LayeredRegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.core.RegistryAccess;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.DataPackResources;
import net.minecraft.server.DispenserRegistry;
import net.minecraft.server.Bootstrap;
import net.minecraft.server.RegistryLayer;
import net.minecraft.server.ReloadableServerResources;
import net.minecraft.server.WorldLoader;
import net.minecraft.server.dedicated.DedicatedServer;
import net.minecraft.server.level.WorldServer;
import net.minecraft.server.packs.EnumResourcePackType;
import net.minecraft.server.packs.repository.ResourcePackLoader;
import net.minecraft.server.packs.repository.ResourcePackRepository;
import net.minecraft.server.packs.repository.ResourcePackSourceVanilla;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.tags.TagKey;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.server.packs.PackType;
import net.minecraft.server.packs.repository.Pack;
import net.minecraft.server.packs.repository.PackRepository;
import net.minecraft.server.packs.repository.ServerPacksSource;
import net.minecraft.server.packs.resources.CloseableResourceManager;
import net.minecraft.server.packs.resources.MultiPackResourceManager;
import net.minecraft.world.flag.FeatureFlags;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.enchantment.Enchantments;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.material.FluidType;
import org.apache.logging.log4j.LogManager;
import org.bukkit.Bukkit;
import org.bukkit.Keyed;
import org.bukkit.NamespacedKey;
import org.bukkit.Registry;
import org.bukkit.World;
import org.bukkit.craftbukkit.v1_21_R1.CraftLootTable;
import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry;
import org.bukkit.craftbukkit.v1_21_R1.CraftServer;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemFactory;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftBlockTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftEntityTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftFluidTag;
import org.bukkit.craftbukkit.v1_21_R1.tag.CraftItemTag;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftMagicNumbers;
import org.bukkit.craftbukkit.v1_21_R1.util.CraftNamespacedKey;
import org.bukkit.craftbukkit.v1_21_R1.util.Versioning;
import org.spigotmc.SpigotWorldConfig;
@@ -99,6 +81,8 @@ public class BukkitInitialization {
synchronized (initLock) {
if (initialized) {
return;
} else {
initialized = true;
}
try {
@@ -111,33 +95,33 @@ public class BukkitInitialization {
instance.setPackage();
// Minecraft Data Init
SharedConstants.a(); // .tryDetectVersion()
DispenserRegistry.a(); // .bootStrap()
SharedConstants.tryDetectVersion();
Bootstrap.bootStrap();
ResourcePackRepository resourcePackRepository = ResourcePackSourceVanilla.c(); // .createVanillaTrustedRepository()
resourcePackRepository.a(); // .reload()
PackRepository resourcePackRepository = ServerPacksSource.createVanillaTrustedRepository();
resourcePackRepository.reload();
ResourceManager resourceManager = new ResourceManager(
EnumResourcePackType.b /* SERVER_DATA */,
resourcePackRepository.c() /* getAvailablePacks() */ .stream().map(ResourcePackLoader::f /* openFull() */).collect(Collectors.toList()));
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.a(); // .createRegistryAccess()
layeredRegistryAccess = WorldLoader.b(resourceManager, layeredRegistryAccess, RegistryLayer.b /* WORLDGEN */, RegistryDataLoader.a /* WORLDGEN_REGISTRIES */); // .loadAndReplaceLayer()
IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().d(); // .compositeAccess().freeze()
// IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().c(); // .compositeAccess().freeze()
CloseableResourceManager resourceManager = new MultiPackResourceManager(
PackType.SERVER_DATA,
resourcePackRepository.getAvailablePacks().stream().map(Pack::open).collect(Collectors.toList())
);
LayeredRegistryAccess<RegistryLayer> layeredRegistryAccess = RegistryLayer.createRegistryAccess();
layeredRegistryAccess = WorldLoader.loadAndReplaceLayer(resourceManager, layeredRegistryAccess, RegistryLayer.WORLDGEN, RegistryDataLoader.WORLDGEN_REGISTRIES);
RegistryAccess.Frozen registryCustom = layeredRegistryAccess.compositeAccess().freeze();
DataPackResources dataPackResources = DataPackResources.a(
resourceManager,
layeredRegistryAccess,
FeatureFlags.d.a() /* REGISTRY.allFlags() */,
CommandDispatcher.ServerType.b /* DEDICATED */,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
ReloadableServerResources dataPackResources = ReloadableServerResources.loadResources(
resourceManager,
layeredRegistryAccess,
FeatureFlags.REGISTRY.allFlags(),
CommandSelection.DEDICATED,
0,
MoreExecutors.directExecutor(),
MoreExecutors.directExecutor()
).join();
dataPackResources.g();
dataPackResources.updateRegistryTags();
try {
IRegistry.class.getName();
RegistryAccess.class.getName();
} catch (Throwable ex) {
ex.printStackTrace();
}
@@ -149,7 +133,7 @@ public class BukkitInitialization {
CraftServer mockedServer = mock(CraftServer.class);
DedicatedServer mockedGameServer = mock(DedicatedServer.class);
when(mockedGameServer.bc()/*registryAccess*/).thenReturn(registryCustom);
when(mockedGameServer.registryAccess()).thenReturn(registryCustom);
when(mockedServer.getLogger()).thenReturn(java.util.logging.Logger.getLogger("Minecraft"));
when(mockedServer.getName()).thenReturn("Mock Server");
@@ -162,61 +146,20 @@ public class BukkitInitialization {
when(mockedServer.getUnsafe()).thenReturn(CraftMagicNumbers.INSTANCE);
when(mockedServer.getLootTable(any())).thenAnswer(invocation -> {
NamespacedKey key = invocation.getArgument(0);
return new CraftLootTable(key, dataPackResources.b().b(CraftLootTable.bukkitKeyToMinecraft(key)));
return new CraftLootTable(key, dataPackResources.fullRegistries().getLootTable(CraftLootTable.bukkitKeyToMinecraft(key)));
});
when(mockedServer.getRegistry(any())).thenAnswer(invocation -> {
Class<Keyed> registryType = invocation.getArgument(0);
Object registry = CraftRegistry.createRegistry(registryType, registryCustom);
// this is very temporary fix to the version mismatch between spigot-api (spigot-repo) and spigot (dmulloy2-repo)
// if you remove this fix please also remove the DummyRegistry class down below
if (registry == null)
return new DummyRegistry<>();
return registry;
});
Class<? extends Keyed> registryType = invocation.getArgument(0);
when(mockedServer.getTag(any(), any(), any())).then(mock -> {
String registry = mock.getArgument(0);
Class<?> clazz = mock.getArgument(2);
MinecraftKey key = CraftNamespacedKey.toMinecraft(mock.getArgument(1));
switch (registry) {
case org.bukkit.Tag.REGISTRY_BLOCKS -> {
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Block namespace must have block type");
TagKey<Block> blockTagKey = TagKey.a(Registries.f, key);
if (BuiltInRegistries.e.b(blockTagKey).isPresent()) {
return new CraftBlockTag(BuiltInRegistries.e, blockTagKey);
}
}
case org.bukkit.Tag.REGISTRY_ITEMS -> {
Preconditions.checkArgument(clazz == org.bukkit.Material.class, "Item namespace must have item type");
TagKey<Item> itemTagKey = TagKey.a(Registries.K, key);
if (BuiltInRegistries.g.b(itemTagKey).isPresent()) {
return new CraftItemTag(BuiltInRegistries.g, itemTagKey);
}
}
case org.bukkit.Tag.REGISTRY_FLUIDS -> {
Preconditions.checkArgument(clazz == org.bukkit.Fluid.class, "Fluid namespace must have fluid type");
TagKey<FluidType> fluidTagKey = TagKey.a(Registries.D, key);
if (BuiltInRegistries.c.b(fluidTagKey).isPresent()) {
return new CraftFluidTag(BuiltInRegistries.c, fluidTagKey);
}
}
case org.bukkit.Tag.REGISTRY_ENTITY_TYPES -> {
Preconditions.checkArgument(clazz == org.bukkit.entity.EntityType.class, "Entity type namespace must have entity type");
TagKey<EntityTypes<?>> entityTagKey = TagKey.a(Registries.z, key);
if (BuiltInRegistries.f.b(entityTagKey).isPresent()) {
return new CraftEntityTag(BuiltInRegistries.f, entityTagKey);
}
}
default -> throw new IllegalArgumentException();
try {
return CraftRegistry.createRegistry(registryType, registryCustom);
} catch (Exception ignored) {
return mock(org.bukkit.Registry.class);
}
return null;
});
WorldServer nmsWorld = mock(WorldServer.class);
ServerLevel nmsWorld = mock(ServerLevel.class);
SpigotWorldConfig mockWorldConfig = mock(SpigotWorldConfig.class);
try {
@@ -237,10 +180,8 @@ public class BukkitInitialization {
CraftRegistry.setMinecraftRegistry(registryCustom);
// Init Enchantments
Enchantments.A.getClass();
Enchantments.AQUA_AFFINITY.getClass();
// Enchantment.stopAcceptingRegistrations();
initialized = true;
}
}
@@ -261,23 +202,4 @@ public class BukkitInitialization {
MinecraftReflectionTestUtil.init();
}
}
class DummyRegistry<T extends Keyed> implements Registry<T> {
@Override
public Iterator<T> iterator() {
return Collections.emptyIterator();
}
@Override
public T get(NamespacedKey key) {
return null;
}
@Override
public Stream<T> stream() {
List<T> emtpy = Collections.emptyList();
return emtpy.stream();
}
}
}
@@ -14,29 +14,22 @@
*/
package com.comphenix.protocol;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.PacketType.Play.Server;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import net.minecraft.network.protocol.login.PacketLoginInStart;
import net.minecraft.network.protocol.login.ServerboundHelloPacket;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author dmulloy2
@@ -260,7 +253,7 @@ public class PacketTypeTest {
@Test
public void testLoginStart() {
// This packet is critical for handleLoin
assertEquals(PacketLoginInStart.class, PacketType.Login.Client.START.getPacketClass());
assertEquals(ServerboundHelloPacket.class, PacketType.Login.Client.START.getPacketClass());
}
@Test
@@ -287,24 +280,6 @@ public class PacketTypeTest {
}
}
@Test
@Disabled // TODO -- lots of constructor parameters :(
public void testCreateMapChunk() {
new PacketContainer(PacketType.Play.Server.MAP_CHUNK);
}
@Test
@Disabled // TODO -- ScoreboardObjective parameter in constructor is causing this to fail
public void testCreateScoreboardObjective() {
new PacketContainer(PacketType.Play.Server.SCOREBOARD_OBJECTIVE);
}
@Test
@Disabled // TODO -- Entity parameter in constructor is causing this to fail
public void testCreateEntitySound() {
new PacketContainer(PacketType.Play.Server.ENTITY_SOUND);
}
@Test
public void testPacketCreation() {
List<PacketType> failed = new ArrayList<>();
@@ -313,12 +288,6 @@ public class PacketTypeTest {
continue;
}
if (type == PacketType.Play.Server.ENTITY_SOUND
|| type == PacketType.Play.Server.SCOREBOARD_OBJECTIVE
|| type == PacketType.Play.Server.MAP_CHUNK) {
continue;
}
try {
new PacketContainer(type);
} catch (Exception ex) {
@@ -0,0 +1,53 @@
/*
* 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<Integer, String> map = BlockingHashMap.create();
ExecutorService service = Executors.newSingleThreadExecutor();
// Create a reader
Future<String> 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");
}
}
@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -38,7 +39,7 @@ import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.cloning.SerializableCloner;
import com.comphenix.protocol.utility.MinecraftMethods;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
@@ -53,11 +54,12 @@ import com.comphenix.protocol.wrappers.EnumWrappers.SoundCategory;
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedAttributeModifier;
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.WrappedDataWatcher.Registry;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
@@ -72,18 +74,21 @@ import net.md_5.bungee.api.chat.ClickEvent;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.HoverEvent;
import net.md_5.bungee.api.chat.hover.content.Text;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.PacketPlayOutGameStateChange;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.game.ClientboundGameEventPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket.AttributeSnapshot;
import net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.effect.MobEffect;
import net.minecraft.world.effect.MobEffectList;
import net.minecraft.world.effect.MobEffectInstance;
import net.minecraft.world.effect.MobEffects;
import net.minecraft.world.entity.ai.attributes.AttributeBase;
import net.minecraft.world.entity.ai.attributes.AttributeInstance;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.AttributeModifier.Operation;
import net.minecraft.world.entity.ai.attributes.Attributes;
import net.minecraft.world.entity.animal.CatVariant;
import net.minecraft.world.entity.animal.FrogVariant;
import org.apache.commons.lang.SerializationUtils;
@@ -201,7 +206,7 @@ public class PacketContainerTest {
}
@Test
@Disabled // TODO
@Disabled // TODO ???
public void testGetStringArrays() {
PacketContainer packet = new PacketContainer(PacketType.Play.Client.UPDATE_SIGN);
this.testObjectArray(packet.getStringArrays(), 0,
@@ -337,7 +342,7 @@ public class PacketContainerTest {
}
@Test
// @Disabled // TODO -- handle type is null
// // @Disabled // TODO -- handle type is null
public void testGetDataValueCollectionModifier() {
PacketContainer entityMetadata = new PacketContainer(PacketType.Play.Server.ENTITY_METADATA);
StructureModifier<List<WrappedDataValue>> watchableAccessor = entityMetadata.getDataValueCollectionModifier();
@@ -345,8 +350,8 @@ public class PacketContainerTest {
assertEquals(0, watchableAccessor.read(0).size());
List<WrappedDataValue> values = Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 21),
new WrappedDataValue(1, Registry.get(String.class), "World"));
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte) 21),
new WrappedDataValue(1, WrappedDataWatcher.Registry.get(String.class), "World"));
// Insert and read back
watchableAccessor.write(0, values);
@@ -374,9 +379,9 @@ public class PacketContainerTest {
}
@Test
@Disabled // TODO
// @Disabled // TODO
public void testSerialization() {
PacketContainer useItem = new PacketContainer(PacketType.Play.Client.USE_ITEM);
PacketContainer useItem = new PacketContainer(PacketType.Play.Client.USE_ITEM_ON);
useItem.getMovingBlockPositions().write(0, new MovingObjectPositionBlock(
new BlockPosition(0, 1, 0),
new Vector(0, 1, 0),
@@ -388,7 +393,7 @@ public class PacketContainerTest {
PacketContainer copy = (PacketContainer) SerializationUtils.clone(useItem);
assertEquals(PacketType.Play.Client.USE_ITEM, copy.getType());
assertEquals(PacketType.Play.Client.USE_ITEM_ON, copy.getType());
assertEquals(Hand.MAIN_HAND, copy.getHands().read(0));
assertEquals(5, copy.getIntegers().read(0));
@@ -504,45 +509,66 @@ public class PacketContainerTest {
}
@Test
@Disabled // TODO -- cloning fails
public void testAttributeList() {
PacketContainer attribute = new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES);
attribute.getIntegers().write(0, 123); // Entity ID
public void testAttributeCloning() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES);
// Initialize some test data
List<AttributeModifier> modifiers = Lists.newArrayList(
new AttributeModifier(MinecraftKey.a("protocollib:test"),10,
AttributeModifier.Operation.a));
WrappedAttribute attribute = WrappedAttribute.newBuilder()
.attributeKey("generic.max_health")
.baseValue(20.0D)
.modifiers(Lists.newArrayList(
WrappedAttributeModifier.newBuilder()
.name("Test")
.amount(1.0D)
.operation(WrappedAttributeModifier.Operation.ADD_NUMBER)
.build()
))
.build();
// Obtain an AttributeSnapshot instance. This is complicated by the fact that AttributeSnapshots
// are inner classes (which is ultimately pointless because AttributeSnapshots don't access any
// members of the packet itself)
PacketPlayOutUpdateAttributes packet = (PacketPlayOutUpdateAttributes) attribute.getHandle();
IRegistry<AttributeBase> registry = BuiltInRegistries.s;
AttributeBase base = registry.a(MinecraftKey.a("generic.max_health"));
AttributeSnapshot snapshot = new AttributeSnapshot(Holder.a(base), 20.0D, modifiers);
attribute.getSpecificModifier(List.class).write(0, Lists.newArrayList(snapshot));
container.getAttributeCollectionModifier().write(0, List.of(attribute));
PacketContainer cloned = attribute.deepClone();
AttributeSnapshot
clonedSnapshot = (AttributeSnapshot) cloned.getSpecificModifier(List.class).read(0).get(0);
ClientboundUpdateAttributesPacket packet = (ClientboundUpdateAttributesPacket) container.getHandle();
// Compare the fields, because apparently the packet is a field in AttributeSnapshot
for (Field field : AttributeSnapshot.class.getDeclaredFields()) {
try {
// Skip the packet
if (field.getType().equals(packet.getClass())) {
continue;
}
PacketContainer cloned = container.deepClone();
ClientboundUpdateAttributesPacket clonedPacket = (ClientboundUpdateAttributesPacket) cloned.getHandle();
field.setAccessible(true);
this.testEquality(field.get(snapshot), field.get(clonedSnapshot));
} catch (AssertionError e) {
throw e;
} catch (Throwable ex) {
ex.printStackTrace();
}
}
assertNotSame(packet, clonedPacket);
assertNotSame(packet.getValues(), clonedPacket.getValues());
List<AttributeSnapshot> values = packet.getValues();
List<AttributeSnapshot> clonedValues = clonedPacket.getValues();
assertEquals(values.size(), clonedValues.size());
assertNotSame(values.get(0), clonedValues.get(0));
AttributeSnapshot snapshot = values.get(0);
AttributeSnapshot clonedSnapshot = clonedValues.get(0);
assertEquals(Attributes.MAX_HEALTH, clonedSnapshot.attribute());
assertEquals(20.0D, clonedSnapshot.base());
assertNotSame(snapshot.modifiers(), clonedSnapshot.modifiers());
}
@Test
public void testReadAttributeList() {
AttributeInstance instance = new AttributeInstance(Attributes.MAX_HEALTH, (__) -> {});
instance.setBaseValue(20.0D);
instance.addTransientModifier(new AttributeModifier(ResourceLocation.parse("protocollib:test"), 1.0,
Operation.ADD_VALUE));
ClientboundUpdateAttributesPacket packet = new ClientboundUpdateAttributesPacket(1234, List.of(instance));
PacketContainer container = PacketContainer.fromPacket(packet);
List<WrappedAttribute> attributes = container.getAttributeCollectionModifier().read(0);
WrappedAttribute attribute = attributes.get(0);
assertEquals("generic.max_health", attribute.getAttributeKey());
assertEquals(20.0D, attribute.getBaseValue());
WrappedAttributeModifier modifier = attribute.getModifiers().iterator().next();
assertEquals(1.0D, modifier.getAmount());
assertEquals("protocollib:test", modifier.getKey().getFullKey());
assertEquals(WrappedAttributeModifier.Operation.ADD_NUMBER, modifier.getOperation());
}
@Test
@@ -569,28 +595,30 @@ public class PacketContainerTest {
@SuppressWarnings("deprecation")
public void testPotionEffect() {
PotionEffect effect = new PotionEffect(PotionEffectType.FIRE_RESISTANCE, 20 * 60, 1);
MobEffect mobEffect = new MobEffect(MobEffects.l, effect.getDuration(),
MobEffectInstance mobEffect = new MobEffectInstance(MobEffects.FIRE_RESISTANCE, effect.getDuration(),
effect.getAmplifier(), effect.isAmbient(),
effect.hasParticles());
int entityId = 42;
ClientboundUpdateMobEffectPacket packet;
// The constructor we want to call
PacketConstructor creator = PacketConstructor.DEFAULT.withPacket(
PacketType.Play.Server.ENTITY_EFFECT, new Class<?>[] { int.class, MobEffect.class, boolean.class });
PacketContainer packet = creator.createPacket(entityId, mobEffect, true);
PacketType.Play.Server.ENTITY_EFFECT, new Class<?>[] { int.class, MobEffectInstance.class, boolean.class });
PacketContainer container = creator.createPacket(entityId, mobEffect, true);
assertEquals(entityId, packet.getIntegers().read(0));
assertEquals(effect.getAmplifier(), packet.getIntegers().read(1));
assertEquals(effect.getDuration(), packet.getIntegers().read(2));
assertEquals(entityId, container.getIntegers().read(0));
assertEquals(effect.getAmplifier(), container.getIntegers().read(1));
assertEquals(effect.getDuration(), container.getIntegers().read(2));
WrappedRegistry registry = WrappedRegistry.getRegistry(MinecraftReflection.getMobEffectListClass());
Object effectList = assertInstanceOf(
MobEffectList.class,
packet.getHolders(MobEffectList.class, InternalStructure.CONVERTER).read(0).getHandle()
MobEffect effectList = assertInstanceOf(
MobEffect.class,
container.getHolders(MobEffect.class, InternalStructure.CONVERTER).read(0).getHandle()
);
assertEquals(effect.getType().getId(), registry.getId(effectList) + 1); // +1 is correct, see CraftPotionEffectType
assertTrue(effectList.getDescriptionId().contains("fire_resistance"));
byte b = 0;
if (effect.isAmbient()) {
@@ -605,7 +633,7 @@ public class PacketContainerTest {
b |= 8;
assertEquals(b, (byte) packet.getBytes().read(0));
assertEquals(b, (byte) container.getBytes().read(0));
}
@Test
@@ -659,6 +687,8 @@ public class PacketContainerTest {
@Disabled // TODO -- need a way to create a structure
public void testInternalStructures() {
PacketContainer container = new PacketContainer(PacketType.Play.Server.SCOREBOARD_TEAM);
container.getModifier().writeDefaults();
Optional<InternalStructure> optStruct = container.getOptionalStructures().read(0);
assertTrue(optStruct.isPresent());
InternalStructure struct = optStruct.get();
@@ -698,7 +728,7 @@ public class PacketContainerTest {
@Test
public void testMovingBlockPos() {
PacketContainer container = new PacketContainer(PacketType.Play.Client.USE_ITEM);
PacketContainer container = new PacketContainer(PacketType.Play.Client.USE_ITEM_ON);
Vector vector = new Vector(0, 1, 2);
BlockPosition position = new BlockPosition(3, 4, 5);
@@ -801,7 +831,7 @@ public class PacketContainerTest {
}
@Test
@Disabled // TODO -- can't create MAP_CHUNK packet
// @Disabled // TODO -- can't create MAP_CHUNK packet
public void testMapChunk() {
// this is a special case as we are generating a data serializer class (we only need to construct the packet)
PacketContainer container = new PacketContainer(PacketType.Play.Server.MAP_CHUNK);
@@ -889,17 +919,25 @@ public class PacketContainerTest {
}
Object buffer = MinecraftReflection.createPacketDataSerializer(0);
MinecraftMethods.getPacketWriteByteBufMethod().invoke(cloned.getHandle(), buffer);
FuzzyReflection fuzzy = FuzzyReflection.fromClass(constructed.getType().getPacketClass(), false);
FieldAccessor accessor = Accessors.getFieldAccessor(fuzzy.getField(FuzzyFieldContract.newBuilder()
.requireModifier(Modifier.STATIC)
.requireModifier(Modifier.PUBLIC)
.typeDerivedOf(StreamCodec.class)
.build()));
StreamCodec codec = (StreamCodec) accessor.get(cloned.getHandle());
codec.encode(buffer, cloned.getHandle());
}
private static final Set<PacketType> CLONE_EXPECTED_FAILURES = Set.of();
@Test
@Disabled // TODO -- cloning is borked
public void testCloning() {
// Try constructing all the packets
Set<PacketType> failures = new HashSet<>();
for (PacketType type : PacketType.values()) {
// TODO: try to support chat - for now chat contains to many sub classes to properly clone it
if (type.isDeprecated() || !type.isSupported() || type.name().contains("CUSTOM_PAYLOAD") || type.name().contains("CHAT")
|| type.name().contains("BUNDLE")) {
if (type.isDeprecated() || !type.isSupported()) {
continue;
}
@@ -911,22 +949,22 @@ public class PacketContainerTest {
// Make sure watchable collections can be cloned
if (type == PacketType.Play.Server.ENTITY_METADATA) {
IRegistry<CatVariant> catVariantRegistry = BuiltInRegistries.ai;
Registry<CatVariant> catVariantRegistry = BuiltInRegistries.CAT_VARIANT;
constructed.getDataValueCollectionModifier().write(0, Lists.newArrayList(
new WrappedDataValue(0, Registry.get(Byte.class), (byte) 1),
new WrappedDataValue(0, Registry.get(Float.class), 5F),
new WrappedDataValue(0, Registry.get(String.class), "String"),
new WrappedDataValue(0, Registry.get(Boolean.class), true),
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Byte.class), (byte) 1),
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Float.class), 5F),
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(String.class), "String"),
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(Boolean.class), true),
new WrappedDataValue(
0,
Registry.getChatComponentSerializer(true),
0,
WrappedDataWatcher.Registry.getChatComponentSerializer(true),
Optional.of(ComponentConverter.fromBaseComponent(TEST_COMPONENT).getHandle())),
new WrappedDataValue(
0,
Registry.getItemStackSerializer(false),
WrappedDataWatcher.Registry.getItemStackSerializer(false),
BukkitConverters.getItemStackConverter().getGeneric(new ItemStack(Material.WOODEN_AXE))),
new WrappedDataValue(0, Registry.get(CatVariant.class), catVariantRegistry.e(CatVariant.e)),
new WrappedDataValue(0, Registry.get(FrogVariant.class), FrogVariant.a)
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(CatVariant.class), catVariantRegistry.get(CatVariant.ALL_BLACK)),
new WrappedDataValue(0, WrappedDataWatcher.Registry.get(FrogVariant.class), FrogVariant.COLD)
));
} else if (type == PacketType.Play.Server.CHAT || type == PacketType.Login.Server.DISCONNECT) {
constructed.getChatComponents().write(0, ComponentConverter.fromBaseComponent(TEST_COMPONENT));
@@ -935,7 +973,7 @@ public class PacketContainerTest {
} else if (type == PacketType.Play.Server.GAME_STATE_CHANGE) {
constructed.getStructures().write(
0,
InternalStructure.getConverter().getSpecific(PacketPlayOutGameStateChange.a));
InternalStructure.getConverter().getSpecific(ClientboundGameEventPacket.ARROW_HIT_PLAYER));
} else if (type == PacketType.Play.Client.USE_ITEM || type == PacketType.Play.Client.BLOCK_PLACE) {
constructed.getLongs().write(0, 0L); // timestamp of the packet, not sent over the network
}
@@ -955,10 +993,13 @@ public class PacketContainerTest {
serializedCloned.getLongs().write(0, 0L);
}
this.assertPacketsEqualAndSerializable(constructed, serializedCloned);
} catch (Throwable t) {
Assertions.fail("Unable to clone " + type, t);
} catch (Throwable ex) {
failures.add(type);
ex.printStackTrace();
}
}
assertEquals(CLONE_EXPECTED_FAILURES, failures);
}
// Convert to objects that support equals()
@@ -1,25 +1,8 @@
package com.comphenix.protocol.injector;
import java.lang.reflect.Field;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.server.level.ChunkProviderServer;
import net.minecraft.server.level.PlayerChunkMap;
import net.minecraft.server.level.PlayerChunkMap.EntityTracker;
import net.minecraft.server.level.WorldServer;
import net.minecraft.world.entity.Entity;
import org.bukkit.craftbukkit.v1_21_R1.CraftWorld;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEntity;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.setFinalField;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import org.junit.jupiter.api.BeforeAll;
public class EntityUtilitiesTest {
@@ -28,10 +11,11 @@ public class EntityUtilitiesTest {
BukkitInitialization.initializeAll();
}
/*
@Test
public void testReflection() {
CraftWorld bukkit = mock(CraftWorld.class);
WorldServer world = mock(WorldServer.class);
ServerLevel world = mock(ServerLevel.class);
when(bukkit.getHandle()).thenReturn(world);
ChunkProviderServer provider = mock(ChunkProviderServer.class);
@@ -57,4 +41,5 @@ public class EntityUtilitiesTest {
.getField(FuzzyFieldContract.newBuilder().typeExact(Int2ObjectMap.class).build());
setFinalField(chunkMap, trackedEntitiesField, trackerMap);
}
*/
}
@@ -1,15 +1,7 @@
package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import io.netty.channel.Channel;
import io.netty.channel.local.LocalServerChannel;
import net.minecraft.network.EnumProtocol;
import net.minecraft.network.NetworkManager;
import net.minecraft.network.protocol.EnumProtocolDirection;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
public class ChannelProtocolUtilTest {
@@ -1,21 +0,0 @@
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());
}
}
@@ -6,6 +6,8 @@ import java.util.List;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import net.minecraft.network.protocol.game.ServerboundUseItemPacket;
import net.minecraft.network.protocol.login.LoginPacketTypes;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -1,5 +1,6 @@
package com.comphenix.protocol.injector.temporary;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import org.bukkit.Server;
@@ -11,8 +12,12 @@ import org.mockito.MockitoAnnotations;
public class TemporaryPlayerFactoryTest {
private static final TemporaryPlayerFactory temporaryPlayerFactory = new TemporaryPlayerFactory();
@Mock
Server server;
@Mock
MinimalInjector minimalInjector;
@BeforeEach
public void initMocks() {
@@ -21,7 +26,17 @@ public class TemporaryPlayerFactoryTest {
@Test
public void testUnavailableSocketInjector() {
Player player = TemporaryPlayerFactory.createTemporaryPlayer();
Player player = temporaryPlayerFactory.createTemporaryPlayer(this.server);
assertThrows(IllegalStateException.class, player::getPlayer);
}
@Test
public void createTemporaryPlayer() {
Player player = temporaryPlayerFactory.createTemporaryPlayer(this.server, this.minimalInjector);
assertEquals(this.server, player.getServer());
// May seem dumb, but this makes sure that the .equals method is still instact.
assertEquals(player, player);
}
}
@@ -1,36 +0,0 @@
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));
}
}
@@ -2,13 +2,11 @@ package com.comphenix.protocol.utility;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import com.comphenix.protocol.BukkitInitialization;
import java.lang.reflect.Field;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.BukkitInitialization;
public class MinecraftMethodsTest {
@BeforeAll
@@ -18,12 +16,8 @@ public class MinecraftMethodsTest {
@Test
public void testSendPacketMethods() {
assertNotNull(MinecraftMethods.getPlayerConnectionSendMethod());
assertNotNull(MinecraftMethods.getPlayerConnectionDisconnectMethod());
assertNotNull(MinecraftMethods.getNetworkManagerSendMethod());
assertNotNull(MinecraftMethods.getNetworkManagerReadPacketMethod());
assertNotNull(MinecraftMethods.getNetworkManagerDisconnectMethod());
assertNotNull(MinecraftMethods.getSendPacketMethod());
assertNotNull(MinecraftMethods.getNetworkManagerHandleMethod());
}
private void setNull(final String fieldName) throws NoSuchFieldException, IllegalAccessException {
@@ -1,16 +1,8 @@
package com.comphenix.protocol.utility;
import com.comphenix.protocol.BukkitInitialization;
import com.mojang.authlib.GameProfile;
import net.minecraft.nbt.NBTCompressedStreamTools;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes;
import net.minecraft.network.protocol.status.ServerPing;
import net.minecraft.network.syncher.DataWatcher;
import net.minecraft.server.network.PlayerConnection;
import net.minecraft.util.MinecraftEncryption;
import net.minecraft.world.level.ChunkCoordIntPair;
import net.minecraft.world.level.block.state.IBlockData;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
@@ -21,13 +13,9 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.junit.jupiter.api.Assertions.*;
import static org.mockito.Mockito.*;
public class MinecraftReflectionTest {
@@ -64,6 +52,7 @@ public class MinecraftReflectionTest {
assertNull(MinecraftReflection.getNullableNMS("ProtocolLib"));
}
/*
@Test
public void testAttributeSnapshot() {
assertEquals(PacketPlayOutUpdateAttributes.AttributeSnapshot.class,
@@ -124,7 +113,7 @@ public class MinecraftReflectionTest {
public void testLoginSignature() {
assertEquals(MinecraftEncryption.b.class, MinecraftReflection.getSaltedSignatureClass());
}
*/
@Test
public void testItemStacks() {
ItemStack stack = new ItemStack(Material.GOLDEN_SWORD);
@@ -2,7 +2,7 @@ package com.comphenix.protocol.utility;
public class MinecraftReflectionTestUtil {
public static final String RELEASE_TARGET = "1.20.6";
public static final String RELEASE_TARGET = "1.21.0";
public static final String PACKAGE_VERSION = "v1_21_R1";
public static final String NMS = "net.minecraft";
public static final String OBC = "org.bukkit.craftbukkit." + PACKAGE_VERSION;
@@ -1,16 +1,15 @@
package com.comphenix.protocol.utility;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
@@ -19,6 +18,11 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.TestUtils.assertItemsEqual;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
public class StreamSerializerTest {
@BeforeAll
@@ -35,7 +39,10 @@ public class StreamSerializerTest {
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
serializer.serializeString(new DataOutputStream(buffer), initial);
DataInputStream input = new DataInputStream(new ByteArrayInputStream(buffer.toByteArray()));
byte[] bytes = buffer.toByteArray();
assertTrue(bytes.length >= initial.length());
DataInputStream input = new DataInputStream(new ByteArrayInputStream(bytes));
String deserialized = serializer.deserializeString(input, 50);
assertEquals(initial, deserialized);
@@ -60,7 +67,7 @@ public class StreamSerializerTest {
}
@Test
@Disabled // TODO -- replaced with registry friendly bytebuf
@Disabled // TODO -- replaced with stream codecs
public void testItems() throws IOException {
StreamSerializer serializer = new StreamSerializer();
ItemStack initial = new ItemStack(Material.STRING);
@@ -72,7 +79,7 @@ public class StreamSerializerTest {
}
@Test
@Disabled // TODO -- replaced with registry friendly bytebuf
@Disabled // TODO -- replaced with stream codecs
public void testItemMeta() throws IOException {
StreamSerializer serializer = new StreamSerializer();
ItemStack initial = new ItemStack(Material.BLUE_WOOL, 2);
@@ -2,24 +2,24 @@ package com.comphenix.protocol.wrappers;
import java.util.Optional;
import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertSame;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.utility.MinecraftReflection;
import net.minecraft.advancements.AdvancementDisplay;
import net.minecraft.advancements.AdvancementFrameType;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.chat.contents.LiteralContents;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static com.comphenix.protocol.utility.MinecraftReflection.getMinecraftClass;
import static org.junit.jupiter.api.Assertions.*;
public class AutoWrapperTest {
/*
@BeforeAll
public static void initializeBukkit() {
BukkitInitialization.initializeAll();
@@ -60,7 +60,7 @@ public class AutoWrapperTest {
(net.minecraft.world.item.ItemStack)MinecraftReflection.getMinecraftItemStack(new ItemStack(Material.ENDER_EYE)),
IChatBaseComponent.b("Test123"),
IChatBaseComponent.b("Test567"),
Optional.of(net.minecraft.resources.MinecraftKey.a("minecraft", "test")),
Optional.of(new net.minecraft.resources.MinecraftKey("minecraft", "test")),
AdvancementFrameType.b,
true,
false,
@@ -117,5 +117,5 @@ public class AutoWrapperTest {
public boolean hidden;
public float x;
public float y;
}
}*/
}
@@ -1,11 +1,14 @@
package com.comphenix.protocol.wrappers;
import java.util.Random;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.utility.TestUtils;
import com.comphenix.protocol.wrappers.Either.Left;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
@@ -17,8 +20,6 @@ import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import java.util.Random;
import static org.junit.jupiter.api.Assertions.*;
public class BukkitConvertersTest {
@@ -29,7 +30,7 @@ public class BukkitConvertersTest {
}
@Test
@Disabled // TODO -- enchantment cannot be applied to this itemstack(???)
// @Disabled // TODO -- enchantment cannot be applied to this itemstack(???)
public void testItemStacks() {
ItemStack item = new ItemStack(Material.DIAMOND_SWORD, 16);
item.addEnchantment(Enchantment.SHARPNESS, 4);
@@ -15,17 +15,17 @@ public class ChunkCoordIntPairTest {
@Test
public void test() {
net.minecraft.world.level.ChunkCoordIntPair pair = new net.minecraft.world.level.ChunkCoordIntPair(1, 2);
net.minecraft.world.level.ChunkPos pair = new net.minecraft.world.level.ChunkPos(1, 2);
ChunkCoordIntPair specific = ChunkCoordIntPair.getConverter().getSpecific(pair);
assertEquals(1, specific.getChunkX());
assertEquals(2, specific.getChunkZ());
net.minecraft.world.level.ChunkCoordIntPair roundtrip =
(net.minecraft.world.level.ChunkCoordIntPair) ChunkCoordIntPair.getConverter().
net.minecraft.world.level.ChunkPos roundtrip =
(net.minecraft.world.level.ChunkPos) ChunkCoordIntPair.getConverter().
getGeneric(specific);
assertEquals(1, roundtrip.e);
assertEquals(2, roundtrip.f);
assertEquals(1, roundtrip.x);
assertEquals(2, roundtrip.z);
}
}
@@ -31,8 +31,8 @@ public class EnumWrappersTest {
public void validateAllEnumFieldsAreWrapped() {
Map<Class<?>, EquivalentConverter<?>> nativeEnums = EnumWrappers.getFromNativeMap();
for (Entry<Class<?>, EquivalentConverter<?>> entry : nativeEnums.entrySet()) {
for (Object nativeConstant : entry.getKey().getEnumConstants()) {
try {
try {
for (Object nativeConstant : entry.getKey().getEnumConstants()) {
// yay, generics
EquivalentConverter<Object> converter = (EquivalentConverter<Object>) entry.getValue();
@@ -44,16 +44,15 @@ public class EnumWrappersTest {
assertNotNull(unwrappedValue);
assertEquals(nativeConstant, unwrappedValue);
} catch (Exception ex) {
fail(ex);
// ex.printStackTrace();
}
} catch (Exception ex) {
fail(ex);
}
}
}
@Test
public void testValidity() {
assertEquals(EnumWrappers.INVALID, KNOWN_INVALID);
assertEquals(KNOWN_INVALID, EnumWrappers.INVALID);
}
}
@@ -2,20 +2,22 @@ package com.comphenix.protocol.wrappers;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.cloning.BukkitCloner;
import com.comphenix.protocol.wrappers.WrappedAttributeModifier.Operation;
import com.google.common.collect.Lists;
import net.minecraft.core.Holder;
import net.minecraft.core.IRegistry;
import net.minecraft.core.Registry;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.protocol.game.PacketPlayOutUpdateAttributes.AttributeSnapshot;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.world.entity.ai.attributes.AttributeBase;
import net.minecraft.network.protocol.game.ClientboundUpdateAttributesPacket.AttributeSnapshot;
import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.entity.ai.attributes.Attribute;
import net.minecraft.world.entity.ai.attributes.AttributeModifier;
import net.minecraft.world.entity.ai.attributes.Attributes;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -35,27 +37,94 @@ public class WrappedAttributeTest {
@BeforeEach
public void setUp() {
// Create a couple of modifiers
this.doubleModifier =
WrappedAttributeModifier.newBuilder().
name("Double Damage").
amount(1).
operation(Operation.ADD_PERCENTAGE).
build();
this.constantModifier =
WrappedAttributeModifier.newBuilder().
name("Damage Bonus").
amount(5).
operation(Operation.ADD_NUMBER).
build();
this.doubleModifier = WrappedAttributeModifier.newBuilder()
.key("protocollib", "double_damage")
.amount(1.0D)
.operation(Operation.ADD_PERCENTAGE)
.build();
// Create attribute
this.attribute = WrappedAttribute.newBuilder().
attributeKey("generic.attackDamage").
baseValue(2).
packet(new PacketContainer(PacketType.Play.Server.UPDATE_ATTRIBUTES)).
modifiers(Lists.newArrayList(this.constantModifier, this.doubleModifier)).
build();
this.constantModifier = WrappedAttributeModifier.newBuilder()
.key("protocollib", "damage_bonus")
.amount(5.0D)
.operation(Operation.ADD_NUMBER)
.build();
this.attribute = WrappedAttribute.newBuilder()
.attributeKey("generic.attackDamage")
.baseValue(2.0D)
.modifiers(Lists.newArrayList(this.constantModifier, this.doubleModifier))
.build();
}
@Test
public void testCreateHandle() {
WrappedAttribute wrapper = WrappedAttribute.newBuilder()
.baseValue(2.0)
.attributeKey("generic.attackDamage")
.modifiers(List.of(constantModifier, doubleModifier))
.build();
AttributeSnapshot handle = (AttributeSnapshot) wrapper.getHandle();
assertNotNull(handle);
assertEquals(2.0D, handle.base());
Attribute attribute = handle.attribute().value();
assertEquals("attribute.name.generic.attack_damage", attribute.getDescriptionId());
Map<ResourceLocation, AttributeModifier> modifiers = handle.modifiers().stream().collect(Collectors.toMap(m -> m.id(), m -> m));
AttributeModifier modifier1 = modifiers.get(ResourceLocation.parse("protocollib:double_damage"));
AttributeModifier modifier2 = modifiers.get(ResourceLocation.parse("protocollib:damage_bonus"));
assertEquals(1.0D, modifier1.amount());
assertEquals(AttributeModifier.Operation.ADD_MULTIPLIED_TOTAL, modifier1.operation());
assertEquals(5.0D, modifier2.amount());
assertEquals(AttributeModifier.Operation.ADD_VALUE, modifier2.operation());
}
@Test
public void testFromHandle() {
AttributeSnapshot handle = new AttributeSnapshot(
Attributes.ATTACK_SPEED,
5.0D,
List.of(new AttributeModifier(ResourceLocation.parse("protocollib:test"),
1.0D, AttributeModifier.Operation.ADD_VALUE))
);
WrappedAttribute wrapper = WrappedAttribute.fromHandle(handle);
assertEquals("generic.attack_speed", wrapper.getAttributeKey());
assertEquals(5.0D, wrapper.getBaseValue());
WrappedAttributeModifier modifier = wrapper.getModifiers().iterator().next();
assertEquals(1.0D, modifier.getAmount());
assertEquals("protocollib:test", modifier.getKey().getFullKey());
assertEquals(Operation.ADD_NUMBER, modifier.getOperation());
}
@Test
public void testCloning() {
AttributeModifier modifier = new AttributeModifier(
ResourceLocation.parse("protocollib:test"),
1.0D,
AttributeModifier.Operation.ADD_VALUE
);
AttributeSnapshot handle = new AttributeSnapshot(
Attributes.ATTACK_SPEED,
5.0D,
List.of(modifier)
);
AttributeSnapshot clone = (AttributeSnapshot) new BukkitCloner().clone(handle);
assertNotSame(handle, clone);
assertEquals(handle.base(), clone.base());
assertEquals(handle.attribute(), clone.attribute());
assertNotSame(handle.modifiers(), clone.modifiers());
assertEquals(1, clone.modifiers().size());
AttributeModifier cloneModifier = clone.modifiers().iterator().next();
assertSame(modifier, cloneModifier);
}
@Test
@@ -94,17 +163,17 @@ public class WrappedAttributeTest {
modifiers.add((AttributeModifier) wrapper.getHandle());
}
IRegistry<AttributeBase> registry = BuiltInRegistries.s;
Registry<Attribute> registry = BuiltInRegistries.ATTRIBUTE;
String attributeKey = attribute.getAttributeKey();
MinecraftKey key = MinecraftKey.a(attributeKey);
AttributeBase base = registry.a(key);
Holder<AttributeBase> holder = registry.e(base);
ResourceLocation key = ResourceLocation.tryParse(attributeKey);
Attribute base = registry.get(key);
Holder<Attribute> holder = registry.wrapAsHolder(base);
return new AttributeSnapshot(holder, attribute.getBaseValue(), modifiers);
}
private AttributeModifier getModifierCopy(WrappedAttributeModifier modifier) {
AttributeModifier.Operation operation = AttributeModifier.Operation.values()[modifier.getOperation().getId()];
return new AttributeModifier((MinecraftKey) com.comphenix.protocol.wrappers.MinecraftKey.getConverter().getGeneric(modifier.getKey()),
return new AttributeModifier(ResourceLocation.parse(modifier.getKey().getFullKey()),
modifier.getAmount(), operation);
}
}
@@ -15,7 +15,8 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.world.level.block.state.IBlockData;
import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.Material;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.type.GlassPane;
@@ -56,14 +57,14 @@ public class WrappedBlockDataTest {
@Test
public void testDataCreation() {
IBlockData nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).o();
BlockState nmsData = CraftMagicNumbers.getBlock(Material.CYAN_STAINED_GLASS_PANE).defaultBlockState();
GlassPane data = (GlassPane) CraftBlockData.fromData(nmsData);
data.setFace(BlockFace.EAST, true);
WrappedBlockData wrapper = WrappedBlockData.createData(data);
assertEquals(wrapper.getType(), Material.CYAN_STAINED_GLASS_PANE);
GlassPane back = new CraftStainedGlassPane((IBlockData) wrapper.getHandle());
GlassPane back = new CraftStainedGlassPane((BlockState) wrapper.getHandle());
assertEquals(back.hasFace(BlockFace.EAST), data.hasFace(BlockFace.EAST));
assertEquals(back.hasFace(BlockFace.SOUTH), data.hasFace(BlockFace.SOUTH));
}
@@ -1,13 +1,13 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import com.google.gson.JsonElement;
import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.text.format.Style;
import net.kyori.adventure.text.format.TextDecoration;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.ChatModifier;
import net.minecraft.ChatFormatting;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -22,7 +22,7 @@ public class WrappedComponentStyleTest {
@Test
public void testComponentStyle() {
ChatModifier style = ChatModifier.a.b(EnumChatFormat.m).a(true);
Object style = net.minecraft.network.chat.Style.EMPTY.applyFormat(ChatFormatting.RED).withBold(true);
WrappedComponentStyle wrapped = new WrappedComponentStyle(style);
JsonElement json = wrapped.getJson();
assertEquals("{\"color\":\"red\",\"bold\":true}", json.toString());
@@ -25,7 +25,7 @@ import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import net.minecraft.world.entity.projectile.EntityEgg;
import net.minecraft.world.entity.projectile.ThrownEgg;
import org.bukkit.Material;
import org.bukkit.Particle;
import org.bukkit.craftbukkit.v1_21_R1.entity.CraftEgg;
@@ -46,7 +46,7 @@ public class WrappedDataWatcherTest {
public static void prepare() {
BukkitInitialization.initializeAll();
EntityEgg nmsEgg = new EntityEgg(null, 0, 0, 0);
ThrownEgg nmsEgg = new ThrownEgg(null, 0, 0, 0);
mockEntity = new CraftEgg(null, nmsEgg);
}
@@ -22,7 +22,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* @author Etrayed
*/
@Disabled // todo: figure this registry update out
// @Disabled // todo: figure this registry update out
public class WrappedLevelChunkDataTest {
@BeforeAll
@@ -1,9 +1,10 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.ChatModifier;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.chat.Style;
import net.minecraft.network.chat.numbers.BlankFormat;
import net.minecraft.network.chat.numbers.FixedFormat;
import net.minecraft.network.chat.numbers.StyledFormat;
@@ -22,13 +23,13 @@ public class WrappedNumberFormatTest {
@Test
void testBlankFormat() {
assertInstanceOf(WrappedNumberFormat.Blank.class, WrappedNumberFormat.fromHandle(BlankFormat.a));
assertEquals(BlankFormat.a, WrappedNumberFormat.blank().getHandle());
assertInstanceOf(WrappedNumberFormat.Blank.class, WrappedNumberFormat.fromHandle(BlankFormat.INSTANCE));
assertEquals(BlankFormat.INSTANCE, WrappedNumberFormat.blank().getHandle());
}
@Test
void testFixedFormat() {
IChatBaseComponent content = IChatBaseComponent.a("Fixed");
Component content = Component.literal("Fixed");
WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new FixedFormat(content));
assertInstanceOf(WrappedNumberFormat.Fixed.class, wrappedHandle);
assertEquals(content, ((WrappedNumberFormat.Fixed) wrappedHandle).getContent().getHandle());
@@ -39,7 +40,8 @@ public class WrappedNumberFormatTest {
@Test
void testStyledFormat() {
ChatModifier style = ChatModifier.a.b(EnumChatFormat.g);
Style style = Style.EMPTY.applyFormat(ChatFormatting.AQUA);
// ChatModifier style = ChatModifier.a.b(EnumChatFormat.g);
WrappedNumberFormat wrappedHandle = WrappedNumberFormat.fromHandle(new StyledFormat(style));
assertInstanceOf(WrappedNumberFormat.Styled.class, wrappedHandle);
assertEquals(style, ((WrappedNumberFormat.Styled) wrappedHandle).getStyle().getHandle());
@@ -17,6 +17,7 @@ import org.junit.jupiter.api.Test;
public class WrappedProfilePublicKeyTest {
/*
private static KeyPair generateKeyPair() throws Exception {
KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
generator.initialize(2048);
@@ -79,5 +80,5 @@ public class WrappedProfilePublicKeyTest {
assertEquals(keyData.getKey(), wrappedKeyData.getKey());
assertEquals(keyData.getExpireTime(), wrappedKeyData.getExpireTime());
assertArrayEquals(keyData.getSignature(), wrappedKeyData.getSignature());
}
}*/
}
@@ -4,7 +4,7 @@ import com.comphenix.protocol.BukkitInitialization;
import java.util.concurrent.ThreadLocalRandom;
import net.minecraft.util.MinecraftEncryption;
import net.minecraft.util.Crypt;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -28,11 +28,11 @@ class WrappedSaltedSignatureTest {
WrappedSaltedSignature loginSignature = new WrappedSaltedSignature(salt, signature);
Object handle = loginSignature.getHandle();
MinecraftEncryption.b data = assertInstanceOf(MinecraftEncryption.b.class, handle);
Crypt.SaltSignaturePair data = assertInstanceOf(Crypt.SaltSignaturePair.class, handle);
assertTrue(data.a());
assertArrayEquals(signature, data.d());
assertEquals(salt, data.c());
assertTrue(data.isValid());
assertArrayEquals(signature, data.signature());
assertEquals(salt, data.salt());
// test key data unwrapping
WrappedSaltedSignature unwrapped = BukkitConverters.getWrappedSignatureConverter().getSpecific(data);
@@ -44,14 +44,15 @@ class WrappedSaltedSignatureTest {
// test key data wrapping
Object wrappedData = BukkitConverters.getWrappedSignatureConverter().getGeneric(loginSignature);
MinecraftEncryption.b wrapped = assertInstanceOf(MinecraftEncryption.b.class, wrappedData);
Crypt.SaltSignaturePair wrapped = assertInstanceOf(Crypt.SaltSignaturePair.class, wrappedData);
assertTrue(wrapped.a());
assertEquals(loginSignature.getSalt(), wrapped.c());
assertArrayEquals(loginSignature.getSignature(), wrapped.d());
assertArrayEquals(loginSignature.getSaltBytes(), wrapped.b());
assertTrue(wrapped.isValid());
assertEquals(loginSignature.getSalt(), wrapped.salt());
assertArrayEquals(loginSignature.getSignature(), wrapped.signature());
assertArrayEquals(loginSignature.getSaltBytes(), wrapped.saltAsBytes());
}
/*
@Test
void testSignedMessageWithoutSignature() {
long salt = ThreadLocalRandom.current().nextLong();
@@ -83,5 +84,5 @@ class WrappedSaltedSignatureTest {
assertEquals(loginSignature.getSalt(), wrapped.c());
assertArrayEquals(loginSignature.getSignature(), wrapped.d());
assertArrayEquals(loginSignature.getSaltBytes(), wrapped.b());
}
}*/
}
@@ -1,17 +1,16 @@
package com.comphenix.protocol.wrappers;
import java.io.IOException;
import java.util.Optional;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.MinecraftProtocolVersion;
import com.comphenix.protocol.wrappers.WrappedServerPing.CompressedImage;
import com.google.common.io.Resources;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import org.yaml.snakeyaml.external.biz.base64Coder.Base64Coder;
@@ -25,21 +24,13 @@ public class WrappedServerPingTest {
}
@Test
@Disabled // TODO MotD is null
public void fullTest() throws IOException {
PacketContainer packet = new PacketContainer(PacketType.Status.Server.SERVER_INFO);
Optional<WrappedServerPing> optionalPing = packet.getServerPings().optionRead(0);
assertTrue(optionalPing.isPresent());
WrappedServerPing serverPing = optionalPing.get();
assertNotNull(serverPing.getMotD());
assertNotNull(serverPing.getFavicon());
assertNotNull(serverPing.getPlayers());
assertNotNull(serverPing.getVersionName());
CompressedImage tux = CompressedImage.fromPng(Resources.getResource("tux.png").openStream());
byte[] original = tux.getDataCopy();
WrappedServerPing serverPing = new WrappedServerPing();
serverPing.setMotD("Hello, this is a test.");
serverPing.setPlayersOnline(5);
serverPing.setPlayersMaximum(10);
@@ -3,12 +3,12 @@ package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import io.netty.buffer.Unpooled;
import net.minecraft.network.PacketDataSerializer;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.network.RegistryFriendlyByteBuf;
import net.minecraft.network.codec.StreamCodec;
import net.minecraft.network.protocol.game.PacketPlayOutOpenBook;
import net.minecraft.network.protocol.game.PacketPlayOutSetSlot;
import net.minecraft.world.EnumHand;
import net.minecraft.network.protocol.game.ClientboundContainerSetSlotPacket;
import net.minecraft.network.protocol.game.ClientboundOpenBookPacket;
import net.minecraft.world.InteractionHand;
import org.bukkit.Material;
import org.bukkit.craftbukkit.v1_21_R1.CraftRegistry;
import org.bukkit.craftbukkit.v1_21_R1.inventory.CraftItemStack;
@@ -26,29 +26,29 @@ public class WrappedStreamCodecTests {
@Test
public void testWithItemStack() {
StreamCodec<RegistryFriendlyByteBuf, PacketPlayOutSetSlot> nmsCodec = PacketPlayOutSetSlot.a;
StreamCodec<RegistryFriendlyByteBuf, ClientboundContainerSetSlotPacket> nmsCodec = ClientboundContainerSetSlotPacket.STREAM_CODEC;
WrappedStreamCodec codec = new WrappedStreamCodec(nmsCodec);
RegistryFriendlyByteBuf buf = new RegistryFriendlyByteBuf(Unpooled.buffer(), CraftRegistry.getMinecraftRegistry());
PacketPlayOutSetSlot packet = new PacketPlayOutSetSlot(1, 2, 3, CraftItemStack.asNMSCopy(new ItemStack(Material.GOLDEN_SHOVEL)));
ClientboundContainerSetSlotPacket packet = new ClientboundContainerSetSlotPacket(1, 2, 3, CraftItemStack.asNMSCopy(new ItemStack(Material.GOLDEN_SHOVEL)));
codec.encode(buf, packet);
PacketPlayOutSetSlot roundTrip = (PacketPlayOutSetSlot) codec.decode(buf);
ClientboundContainerSetSlotPacket roundTrip = (ClientboundContainerSetSlotPacket) codec.decode(buf);
assertEquals(Material.GOLDEN_SHOVEL, CraftItemStack.asBukkitCopy(roundTrip.f()).getType());
assertEquals(Material.GOLDEN_SHOVEL, CraftItemStack.asBukkitCopy(roundTrip.getItem()).getType());
}
@Test
public void testWithStandardSerializer() {
StreamCodec<PacketDataSerializer, PacketPlayOutOpenBook> nmsCodec = PacketPlayOutOpenBook.a;
StreamCodec<FriendlyByteBuf, ClientboundOpenBookPacket> nmsCodec = ClientboundOpenBookPacket.STREAM_CODEC;
WrappedStreamCodec codec = new WrappedStreamCodec(nmsCodec);
PacketDataSerializer buf = new PacketDataSerializer(Unpooled.buffer());
PacketPlayOutOpenBook packet = new PacketPlayOutOpenBook(EnumHand.a);
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
ClientboundOpenBookPacket packet = new ClientboundOpenBookPacket(InteractionHand.OFF_HAND);
codec.encode(buf, packet);
PacketPlayOutOpenBook roundTrip = (PacketPlayOutOpenBook) codec.decode(buf);
ClientboundOpenBookPacket roundTrip = (ClientboundOpenBookPacket) codec.decode(buf);
assertEquals(EnumHand.a, roundTrip.b());
assertEquals(InteractionHand.OFF_HAND, roundTrip.getHand());
}
}
@@ -1,9 +1,10 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.EnumChatFormat;
import net.minecraft.network.chat.IChatBaseComponent;
import net.minecraft.network.protocol.game.PacketPlayOutScoreboardTeam;
import net.minecraft.ChatFormatting;
import net.minecraft.network.chat.Component;
import net.minecraft.network.protocol.game.ClientboundSetPlayerTeamPacket;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
@@ -17,9 +18,9 @@ public class WrappedTeamParametersTest {
@Test
void testTeamParameters() {
IChatBaseComponent displayName = IChatBaseComponent.b("display name");
IChatBaseComponent prefix = IChatBaseComponent.b("prefix");
IChatBaseComponent suffix = IChatBaseComponent.b("suffix");
Component displayName = Component.literal("display name");
Component prefix = Component.literal("prefix");
Component suffix = Component.literal("suffix");
String nametagVisibility = "always";
String collisionRule = "never";
@@ -41,13 +42,13 @@ public class WrappedTeamParametersTest {
assertEquals(EnumWrappers.ChatFormatting.RED, wrapped.getColor());
assertEquals(1, wrapped.getOptions());
PacketPlayOutScoreboardTeam.b handle = (PacketPlayOutScoreboardTeam.b) wrapped.getHandle();
assertEquals(handle.a(), displayName);
assertEquals(handle.f(), prefix);
assertEquals(handle.g(), suffix);
assertEquals(handle.d(), nametagVisibility);
assertEquals(handle.e(), collisionRule);
assertEquals(handle.c(), EnumChatFormat.m);
assertEquals(handle.b(), 1);
ClientboundSetPlayerTeamPacket.Parameters handle = (ClientboundSetPlayerTeamPacket.Parameters) wrapped.getHandle();
assertEquals(handle.getDisplayName(), displayName);
assertEquals(handle.getPlayerPrefix(), prefix);
assertEquals(handle.getPlayerSuffix(), suffix);
assertEquals(handle.getNametagVisibility(), nametagVisibility);
assertEquals(handle.getCollisionRule(), collisionRule);
assertEquals(handle.getColor(), ChatFormatting.RED);
assertEquals(handle.getOptions(), 1);
}
}
@@ -17,23 +17,25 @@
package com.comphenix.protocol.wrappers.nbt;
import static org.junit.jupiter.api.Assertions.assertEquals;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.nbt.io.NbtBinarySerializer;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Disabled;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class NbtFactoryTest {
@BeforeAll
@@ -64,9 +66,9 @@ public class NbtFactoryTest {
}
@Test
@Disabled // TODO
@Disabled // TODO -- item stacks don't have tags anymore
public void testItemTag() {
ItemStack test = new ItemStack(Items.L);
ItemStack test = new ItemStack(Items.ACACIA_DOOR);
org.bukkit.inventory.ItemStack craftTest = MinecraftReflection.getBukkitItemStack(test);
NbtCompound compound = NbtFactory.ofCompound("tag");