Compare commits

...
14 Commits
Author SHA1 Message Date
Dan Mulloy 3442d3fb8f Update version to 5.3.0 for release 2024-10-20 17:12:58 -05:00
Pasqual Koschmieder 1e3fa2d36e deps: update bytebuddy and shadow (#3247) 2024-09-24 21:53:32 +02:00
JustRed23 c9ee2f16e2 Fix serializing and deserializing of ItemStack on 1.21 (#3217) 2024-09-23 12:30:34 +00:00
Dan Mulloy 7f615378e4 Compile against 1.21.1 2024-09-22 21:41:32 -05:00
SzczurekYT d636be81e7 Fix ConcurrentModificationException when shutting down async listeners (#3169) 2024-09-23 00:11:17 +00:00
Bjorn Beishline 3cb261f466 Fix ConcurrentModificationException in PacketListenerSet (#3241) 2024-09-23 00:04:12 +00:00
Maurice Eisenblätter 7338183ed6 Fix Github actions & unit tests (#3244)
* chore: update github actions

* test: fix initialization

* test: add missing imports for test initialization
2024-09-22 19:00:57 -05:00
iiAhmedYT fb085676a9 Add AdventureComponentConverter#fromComponentObject (#3127) 2024-07-23 02:59:30 +00:00
Diogo Correia 9fc68d4b6c fix: race condition in EnumWrappers#initialize (#3121)
Adds synchronization primitives inside the initialization function to
ensure that when the function returns, everything is guaranteed to be
initialized.

Fixes #3120
2024-07-22 21:56:48 -05:00
Dan Mulloy f227b17f5f fix: test instantiation 2024-07-22 21:52:43 -05:00
Scott Hiett 4aa344bab2 Fix WrappedTeamParameters.Builder incorrectly using displayName for prefix and suffix (#3050) 2024-06-27 03:12:12 +00:00
Maurice Eisenblätter da65c9d3c1 Improve/cleanup injector (part 2) (#3042)
* fix(#3024): cache noop accessor on ctor lookup failure
* fix(#3023): add client intent enum wrapper
2024-06-25 21:07:16 -05:00
Maurice Eisenblätter 47bcd53fd0 Improve/cleanup injector (#2992) 2024-06-16 20:42:46 -05:00
Dan Mulloy 2cad996252 fix: gracefully fail for unknown protocols
Fixes #3012
Fixes #3001
Fixes #2994
2024-06-14 14:00:59 -05:00
52 changed files with 1275 additions and 2068 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@v3
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
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@v2
uses: actions/upload-artifact@v4
with:
name: ProtocolLib
path: build/libs/ProtocolLib.jar
+4 -4
View File
@@ -20,17 +20,17 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v3
uses: actions/checkout@v4
- name: Setup java
uses: actions/setup-java@v3
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '21'
cache: 'gradle'
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
uses: github/codeql-action/init@v3
with:
languages: 'java'
@@ -38,4 +38,4 @@ jobs:
run: ./gradlew build -x test --no-daemon
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
uses: github/codeql-action/analyze@v3
+6 -6
View File
@@ -1,11 +1,11 @@
plugins {
id 'java-library'
id 'maven-publish'
id 'com.github.johnrengelman.shadow' version '8.1.1'
id 'com.gradleup.shadow' version '8.3.2'
}
group = 'com.comphenix.protocol'
version = '5.3.0-SNAPSHOT'
version = '5.3.0'
description = 'Provides access to the Minecraft protocol'
def isSnapshot = version.endsWith('-SNAPSHOT')
@@ -33,9 +33,9 @@ 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'
implementation 'net.bytebuddy:byte-buddy:1.15.1'
compileOnly 'org.spigotmc:spigot-api:1.21.1-R0.1-SNAPSHOT'
compileOnly 'org.spigotmc:spigot:1.21.1-R0.1-SNAPSHOT'
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.1-R0.1-SNAPSHOT'
testImplementation 'net.kyori:adventure-text-serializer-gson:4.14.0'
testImplementation 'net.kyori:adventure-text-serializer-plain:4.14.0'
}
@@ -741,11 +741,7 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
STATUS("Status", "status"),
LOGIN("Login", "login"),
CONFIGURATION("Configuration", "configuration"),
/**
* Only for packets removed in Minecraft 1.7.2
*/
LEGACY("", "");
UNKNOWN("", "");
private final String packetName;
private final String mojangName;
@@ -761,14 +757,17 @@ public class PacketType implements Serializable, Cloneable, Comparable<PacketTyp
* @return The corresponding protocol.
*/
public static Protocol fromVanilla(Enum<?> vanilla) {
if (vanilla == null) {
return UNKNOWN;
}
switch (vanilla.name()) {
case "HANDSHAKING": return HANDSHAKING;
case "PLAY": return PLAY;
case "STATUS": return STATUS;
case "LOGIN": return LOGIN;
case "CONFIGURATION": return CONFIGURATION;
default:
throw new IllegalArgumentException("Unrecognized vanilla enum " + vanilla);
default: return UNKNOWN;
}
}
@@ -17,9 +17,7 @@ package com.comphenix.protocol;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.comphenix.protocol.utility.MinecraftVersion;
import java.util.List;
import com.google.common.collect.ImmutableList;
import org.apache.commons.lang.Validate;
import org.bukkit.plugin.Plugin;
@@ -37,17 +35,12 @@ public class ProtocolLibrary {
/**
* The maximum version ProtocolLib has been tested with.
*/
public static final String MAXIMUM_MINECRAFT_VERSION = "1.20.4";
public static final String MAXIMUM_MINECRAFT_VERSION = "1.21.1";
/**
* The date (with ISO 8601 or YYYY-MM-DD) when the most recent version (1.20.4) was released.
*/
public static final String MINECRAFT_LAST_RELEASE_DATE = "2023-12-07";
/**
* Plugins that are currently incompatible with ProtocolLib.
*/
public static final List<String> INCOMPATIBLE = ImmutableList.of("TagAPI");
public static final String MINECRAFT_LAST_RELEASE_DATE = "2024-08-08";
private static Plugin plugin;
private static ProtocolConfig config;
@@ -17,14 +17,6 @@
package com.comphenix.protocol.async;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketStream;
import com.comphenix.protocol.PacketType;
@@ -37,8 +29,16 @@ import com.comphenix.protocol.injector.collection.InboundPacketListenerSet;
import com.comphenix.protocol.injector.collection.OutboundPacketListenerSet;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Represents a filter manager for asynchronous packets.
@@ -284,9 +284,9 @@ public class AsyncFilterManager implements AsynchronousManager {
}
private void unregisterAsyncHandlers(PacketProcessingQueue processingQueue, Plugin plugin) {
// Iterate through every packet listener
for (AsyncListenerHandler listener : processingQueue.values()) {
for (AsyncListenerHandler listener : ImmutableList.copyOf(processingQueue.values())) {
// Remove the listener
if (Objects.equal(listener.getPlugin(), plugin)) {
unregisterAsyncHandler(listener);
@@ -607,8 +607,6 @@ 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);
@@ -1,494 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.google.common.base.Objects;
import com.google.common.collect.Range;
import java.util.HashSet;
import java.util.Map;
import java.util.NavigableMap;
import java.util.Set;
import java.util.TreeMap;
/**
* Represents a generic store of intervals and associated values. No two intervals
* can overlap in this representation.
* <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);
}
@@ -1,266 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import com.comphenix.protocol.utility.SafeCacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.RemovalCause;
import com.google.common.cache.RemovalListener;
import com.google.common.cache.RemovalNotification;
import java.util.Collection;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
/**
* A map that supports blocking on read operations. Null keys are not supported.
* <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,8 +30,12 @@ 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;
@@ -41,11 +45,6 @@ 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.
*
@@ -479,7 +478,7 @@ public class DetailedErrorReporter implements ErrorReporter {
} catch (LinkageError ex) {
// Apache is probably missing
apacheCommonsMissing = true;
} catch (ThreadDeath | OutOfMemoryError e) {
} catch (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,17 +1,18 @@
package com.comphenix.protocol.events;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.*;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
@@ -25,10 +26,49 @@ import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nonnull;
import java.lang.reflect.Array;
import java.time.Instant;
import java.util.*;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.StreamSerializer;
import com.comphenix.protocol.wrappers.BlockPosition;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.ChunkCoordIntPair;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.CustomPacketPayloadWrapper;
import com.comphenix.protocol.wrappers.Either;
import com.comphenix.protocol.wrappers.EnumWrappers;
import com.comphenix.protocol.wrappers.MinecraftKey;
import com.comphenix.protocol.wrappers.MovingObjectPositionBlock;
import com.comphenix.protocol.wrappers.MultiBlockChangeInfo;
import com.comphenix.protocol.wrappers.Pair;
import com.comphenix.protocol.wrappers.PlayerInfoData;
import com.comphenix.protocol.wrappers.WrappedAttribute;
import com.comphenix.protocol.wrappers.WrappedBlockData;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedDataValue;
import com.comphenix.protocol.wrappers.WrappedDataWatcher;
import com.comphenix.protocol.wrappers.WrappedEnumEntityUseAction;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedLevelChunkData;
import com.comphenix.protocol.wrappers.WrappedMessageSignature;
import com.comphenix.protocol.wrappers.WrappedNumberFormat;
import com.comphenix.protocol.wrappers.WrappedParticle;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.comphenix.protocol.wrappers.WrappedRegistrable;
import com.comphenix.protocol.wrappers.WrappedRegistry;
import com.comphenix.protocol.wrappers.WrappedRemoteChatSessionData;
import com.comphenix.protocol.wrappers.WrappedSaltedSignature;
import com.comphenix.protocol.wrappers.WrappedServerPing;
import com.comphenix.protocol.wrappers.WrappedStatistic;
import com.comphenix.protocol.wrappers.WrappedTeamParameters;
import com.comphenix.protocol.wrappers.WrappedWatchableObject;
import com.comphenix.protocol.wrappers.nbt.NbtBase;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
import com.google.common.base.Preconditions;
public abstract class AbstractStructure {
protected transient Object handle;
@@ -1134,6 +1174,17 @@ public 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,6 +13,7 @@ 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,7 +17,12 @@
package com.comphenix.protocol.events;
import java.util.*;
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 com.comphenix.protocol.PacketType;
import com.comphenix.protocol.injector.GamePhase;
@@ -90,20 +95,6 @@ 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.
*
@@ -311,6 +302,7 @@ public class ListeningWhitelist {
* @param gamePhase - the gamephase.
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhase(GamePhase gamePhase) {
this.gamePhase = gamePhase;
return this;
@@ -321,6 +313,7 @@ public class ListeningWhitelist {
*
* @return This builder, for chaining.
*/
@Deprecated
public Builder gamePhaseBoth() {
return gamePhase(GamePhase.BOTH);
}
@@ -22,6 +22,7 @@ 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 ListenerInvoker {
public interface ListenerManager {
boolean hasInboundListener(PacketType packetType);
@@ -38,24 +38,12 @@ public interface ListenerInvoker {
*
* @param event - the packet event to invoke.
*/
void invokePacketReceiving(PacketEvent event);
void invokeInboundPacketListeners(PacketEvent event);
/**
* Invokes the given packet event for every registered listener.
*
* @param event - the packet event to invoke.
*/
void invokePacketSending(PacketEvent event);
/**
* Retrieve the associated type of a packet.
*
* @param packet - the packet.
* @return The packet type.
* @deprecated use
* {@link com.comphenix.protocol.injector.packet.PacketRegistry#getPacketType(PacketType.Protocol, Class)}
* instead.
*/
@Deprecated
PacketType getPacketType(Object packet);
void invokeOutboundPacketListeners(PacketEvent event);
}
@@ -1,5 +1,7 @@
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;
@@ -7,7 +9,6 @@ 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.
@@ -43,7 +44,7 @@ public class NetworkProcessor {
for (PacketPostListener listener : marker.getPostListeners()) {
try {
listener.onPostEvent(event);
} catch (OutOfMemoryError | ThreadDeath e) {
} catch (OutOfMemoryError e) {
throw e;
} catch (Throwable e) {
this.reporter.reportMinimal(listener.getPlugin(), "SentListener.run()", e);
@@ -172,8 +172,6 @@ public class PacketConstructor {
result = unwrapper.unwrapItem(values[i]);
} catch (OutOfMemoryError e) {
throw e;
} catch (ThreadDeath e) {
throw e;
} catch (Throwable e) {
lastException = e;
}
@@ -46,15 +46,10 @@ 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;
import io.netty.channel.Channel;
public class PacketFilterManager implements ListenerInvoker, InternalManager {
public class PacketFilterManager implements ListenerManager, InternalManager {
// plugin verifier reports
private static final ReportType PLUGIN_VERIFIER_ERROR = new ReportType("Plugin verifier error: %s");
@@ -80,7 +75,6 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
private final Set<PacketListener> registeredListeners;
// injectors
private final PlayerInjectionHandler playerInjectionHandler;
private final NetworkManagerInjector networkManagerInjector;
// status of this manager
@@ -111,10 +105,8 @@ public class PacketFilterManager implements ListenerInvoker, 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();
@@ -122,7 +114,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
// hook into all connected players
for (Player player : this.server.getOnlinePlayers()) {
this.playerInjectionHandler.injectPlayer(player, ConflictStrategy.BAIL_OUT);
this.networkManagerInjector.getInjector(player).inject();
}
}
@@ -180,7 +172,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
// process outbound
this.playerInjectionHandler.sendServerPacket(receiver, packet, marker, filters);
this.networkManagerInjector.getInjector(receiver).sendClientboundPacket(packet.getHandle(), marker, filters);
}
}
@@ -192,13 +184,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@Override
public void sendWirePacket(Player receiver, WirePacket packet) {
if (!this.closed) {
// 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);
this.networkManagerInjector.getInjector(receiver).sendWirePacket(packet);
}
}
@@ -227,7 +213,7 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
if (filters) {
// post to all listeners
PacketEvent event = PacketEvent.fromClient(this.networkManagerInjector, packet, null, sender);
this.invokePacketReceiving(event);
this.invokeInboundPacketListeners(event);
if (event.isCancelled()) {
return;
}
@@ -240,13 +226,13 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
// post to the player inject, reset our cancel state change
this.playerInjectionHandler.receiveClientPacket(sender, nmsPacket);
this.networkManagerInjector.getInjector(sender).readServerboundPacket(nmsPacket);
}
}
@Override
public int getProtocolVersion(Player player) {
return this.playerInjectionHandler.getProtocolVersion(player);
return this.networkManagerInjector.getInjector(player).getProtocolVersion();
}
@Override
@@ -447,12 +433,12 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
PacketFilterManager.this.playerInjectionHandler.updatePlayer(event.getPlayer());
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.MONITOR)
@@ -518,33 +504,19 @@ public class PacketFilterManager implements ListenerInvoker, InternalManager {
}
@Override
public void invokePacketReceiving(PacketEvent event) {
public void invokeInboundPacketListeners(PacketEvent event) {
if (!this.closed) {
this.postPacketToListeners(this.inboundListeners, event, false);
}
}
@Override
public void invokePacketSending(PacketEvent event) {
public void invokeOutboundPacketListeners(PacketEvent event) {
if (!this.closed) {
this.postPacketToListeners(this.outboundListeners, event, true);
}
}
@Override
public PacketType getPacketType(Object packet) {
if (!MinecraftReflection.isPacketClass(packet)) {
throw new IllegalArgumentException("Given packet is not a minecraft packet instance");
}
PacketType type = PacketRegistry.getPacketType(packet.getClass());
if (type != null) {
return type;
}
throw new IllegalArgumentException("Unable to associate given packet " + packet + " with a registered packet!");
}
private void postPacketToListeners(PacketListenerSet listeners, PacketEvent event, boolean outbound) {
try {
// append async marker if any async listener for the packet was registered
@@ -1,5 +1,6 @@
package com.comphenix.protocol.injector.collection;
import java.util.Iterator;
import java.util.Set;
import javax.annotation.Nullable;
@@ -45,7 +46,9 @@ public abstract class PacketListenerSet {
public void addListener(PacketListener packetListener) {
ListeningWhitelist listeningWhitelist = getListeningWhitelist(packetListener);
for (PacketType packetType : listeningWhitelist.getTypes()) {
for (Iterator<PacketType> packet = listeningWhitelist.getTypes().iterator(); packet.hasNext();) {
PacketType packetType = packet.next();
Set<PacketType> supportedPacketTypes = (packetType.getSender() == Sender.SERVER)
? PacketRegistry.getServerPacketTypes()
: PacketRegistry.getClientPacketTypes();
@@ -56,7 +59,7 @@ public abstract class PacketListenerSet {
.build());
// remove unknown packet types
listeningWhitelist.getTypes().remove(packetType);
packet.remove();
}
}
@@ -1,59 +0,0 @@
package com.comphenix.protocol.injector.netty;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
/**
* Represents a listener for received or sent packets.
*
* @author Kristian
*/
public interface ChannelListener {
/**
* Invoked when a packet is being sent to the client.
* <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,9 +1,12 @@
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.
@@ -12,6 +15,8 @@ import org.bukkit.entity.Player;
*/
public interface Injector {
SocketAddress getAddress();
/**
* Retrieve the current protocol version of the player.
*
@@ -23,12 +28,8 @@ 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.
*/
boolean inject();
void uninject();
void inject();
/**
* Close the current injector.
@@ -42,20 +43,13 @@ public interface Injector {
* @param marker - the network marker.
* @param filtered - whether or not the packet is filtered.
*/
void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered);
void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered);
void receiveClientPacket(Object packet);
void readServerboundPacket(Object packet);
/**
* 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);
}
void sendWirePacket(WirePacket packet);
void disconnect(String message);
/**
* Retrieve the current protocol state. Note that since 1.20.2 the client and server direction can be in different
@@ -66,22 +60,6 @@ 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.
*
@@ -96,7 +74,7 @@ public interface Injector {
*/
void setPlayer(Player player);
void disconnect(String message);
boolean isConnected();
/**
* Determine if the channel has already been injected.
@@ -1,10 +1,14 @@
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 org.bukkit.entity.Player;
import com.comphenix.protocol.injector.netty.WirePacket;
final class EmptyInjector implements Injector {
@@ -16,18 +20,21 @@ 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 boolean inject() {
return false;
}
@Override
public void uninject() {
public void inject() {
}
@Override
@@ -35,11 +42,19 @@ final class EmptyInjector implements Injector {
}
@Override
public void sendServerPacket(Object packet, NetworkMarker marker, boolean filtered) {
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
}
@Override
public void receiveClientPacket(Object packet) {
public void readServerboundPacket(Object packet) {
}
@Override
public void sendWirePacket(WirePacket packet) {
}
@Override
public void disconnect(String message) {
}
@Override
@@ -47,15 +62,6 @@ 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;
@@ -67,7 +73,8 @@ final class EmptyInjector implements Injector {
}
@Override
public void disconnect(String message) {
public boolean isConnected() {
return false;
}
@Override
@@ -2,7 +2,8 @@ package com.comphenix.protocol.injector.netty.channel;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftReflection;
@@ -12,39 +13,38 @@ import io.netty.channel.ChannelInboundHandlerAdapter;
final class InboundPacketInterceptor extends ChannelInboundHandlerAdapter {
private final NettyChannelInjector injector;
private final ChannelListener channelListener;
public InboundPacketInterceptor(NettyChannelInjector injector, ChannelListener listener) {
public InboundPacketInterceptor(NettyChannelInjector injector) {
this.injector = injector;
this.channelListener = listener;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (MinecraftReflection.isPacketClass(msg)) {
// 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);
// try get packet type
PacketType.Protocol protocol = this.injector.getInboundProtocol();
PacketType packetType = PacketRegistry.getPacketType(protocol, msg.getClass());
if (protocol == Protocol.UNKNOWN) {
ProtocolLogger.debug("skipping unknown inbound protocol for {0}", msg.getClass());
ctx.fireChannelRead(msg);
return;
}
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 post the
// check if there are any listeners bound for the packet - if not just send the
// packet down the pipeline
if (!this.channelListener.hasInboundListener(packetType)) {
if (!this.injector.hasInboundListener(packetType)) {
ctx.fireChannelRead(msg);
return;
}
// call all inbound listeners
this.injector.processInboundPacket(ctx, msg, packetType);
// don't invoke next handler and transfer packet to injector for further processing
this.injector.processInbound(ctx, new PacketContainer(packetType, msg));
} else {
// just pass the message down the pipeline
ctx.fireChannelRead(msg);
@@ -14,21 +14,25 @@
*/
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.netty.ChannelListener;
import com.comphenix.protocol.injector.ListenerManager;
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 java.util.concurrent.ConcurrentMap;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import io.netty.channel.ChannelHandler;
/**
* Represents an injector factory.
@@ -39,24 +43,23 @@ import org.bukkit.plugin.Plugin;
*/
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, Server server, ErrorReporter errorReporter) {
public InjectionFactory(Plugin plugin, ErrorReporter errorReporter, ListenerManager listenerManager) {
this.plugin = plugin;
this.server = server;
this.errorReporter = errorReporter;
this.listenerManager = listenerManager;
}
/**
@@ -72,11 +75,10 @@ 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, ChannelListener listener) {
public Injector fromPlayer(Player player) {
if (this.closed) {
return new EmptyInjector(player);
}
@@ -114,10 +116,9 @@ public class InjectionFactory {
// construct a new injector as it seems like we have none yet
injector = new NettyChannelInjector(
player,
this.server,
networkManager,
channel,
listener,
this.listenerManager,
this,
this.errorReporter);
this.cacheInjector(player, injector);
@@ -155,31 +156,27 @@ 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, ChannelListener listener, TemporaryPlayerFactory playerFactory) {
public Injector fromChannel(Channel channel) {
if (this.closed) {
return EmptyInjector.WITHOUT_PLAYER;
}
Object netManager = this.findNetworkManager(channel);
Player temporaryPlayer = playerFactory.createTemporaryPlayer(this.server);
Object networkManager = this.findNetworkManager(channel);
Player temporaryPlayer = TemporaryPlayerFactory.createTemporaryPlayer();
NettyChannelInjector injector = new NettyChannelInjector(
temporaryPlayer,
this.server,
netManager,
networkManager,
channel,
listener,
this.listenerManager,
this,
this.errorReporter);
MinimalInjector minimalInjector = new NettyChannelMinimalInjector(injector);
// Initialize temporary player
TemporaryPlayerFactory.setInjectorInPlayer(temporaryPlayer, minimalInjector);
TemporaryPlayerFactory.setInjectorForPlayer(temporaryPlayer, injector);
return injector;
}
@@ -235,9 +232,9 @@ public class InjectionFactory {
* @return The associated injector, or NULL if this is a Bukkit player.
*/
private NettyChannelInjector getTemporaryInjector(Player player) {
MinimalInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelMinimalInjector) {
return ((NettyChannelMinimalInjector) injector).getInjector();
Injector injector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
if (injector instanceof NettyChannelInjector) {
return (NettyChannelInjector) injector;
}
return null;
@@ -250,11 +247,12 @@ public class InjectionFactory {
* @return The network manager.
*/
private Object findNetworkManager(Channel channel) {
// Find the network manager
Object networkManager = NettyChannelInjector.findChannelHandler(channel,
MinecraftReflection.getNetworkManagerClass());
if (networkManager != null) {
return networkManager;
Class<?> networkManagerClass = MinecraftReflection.getNetworkManagerClass();
for (Entry<String, ChannelHandler> entry : channel.pipeline()) {
if (networkManagerClass.isAssignableFrom(entry.getValue().getClass())) {
return entry.getValue();
}
}
throw new IllegalArgumentException("Unable to find NetworkManager in " + channel);
@@ -2,16 +2,16 @@ 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.Server;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
@@ -24,24 +24,21 @@ 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;
@@ -49,59 +46,45 @@ import io.netty.util.AttributeKey;
public class NettyChannelInjector implements Injector {
// 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 INBOUND_INTERCEPTOR_NAME = "protocol_lib_inbound_interceptor";
private static final String INBOUND_PROTOCOL_GETTER_NAME = "protocol_lib_inbound_protocol_getter";
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[] PROTOCOL_LIB_HANDLERS = new String[]{
WIRE_PACKET_ENCODER_NAME, INTERCEPTOR_NAME, PROTOCOL_READER_NAME
private static final String[] NETTY_HANDLER_NAMES = new String[]{
WIRE_PACKET_ENCODER_NAME, INBOUND_INTERCEPTOR_NAME, INBOUND_PROTOCOL_GETTER_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);
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();
// use random attribute name because they need to be unique and would throw on reload
private static final AttributeKey<Integer> PROTOCOL_VERSION = AttributeKey.valueOf(getRandomKey());
private static final AttributeKey<NettyChannelInjector> INJECTOR = AttributeKey.valueOf(getRandomKey());
// 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;
static NettyChannelInjector findInjector(Channel channel) {
return channel.attr(INJECTOR).get();
}
// bukkit stuff
private final Server server;
private static String getRandomKey() {
return "ProtocolLib-" + ThreadLocalRandom.current().nextLong();
}
// 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 wrappedChannel;
private final ChannelListener channelListener;
private final InjectionFactory injectionFactory;
private final Channel channel;
private final FieldAccessor channelField;
@@ -111,47 +94,44 @@ public class NettyChannelInjector implements Injector {
protected final ThreadLocal<Boolean> processedPackets = ThreadLocal.withInitial(() -> Boolean.FALSE);
// status of this injector
private volatile boolean closed = false;
private final AtomicBoolean closed = new AtomicBoolean(false);
private volatile boolean injected = false;
// information about the player belonging to this injector
private String playerName;
private Player resolvedPlayer;
private Player player;
// lazy initialized fields, if we don't need them we don't bother about them
private Object playerConnection;
private InboundProtocolReader inboundProtocolReader;
private volatile InboundProtocolReader inboundProtocolReader;
public NettyChannelInjector(
Player player,
Server server,
Object netManager,
Object networkManager,
Channel channel,
ChannelListener listener,
ListenerManager listenerManager,
InjectionFactory injector,
ErrorReporter errorReporter
) {
// bukkit stuff
this.server = server;
this.resolvedPlayer = player;
this.player = player;
// protocol lib stuff
this.errorReporter = errorReporter;
this.networkProcessor = new NetworkProcessor(errorReporter);
this.listenerInvoker = new PacketListenerInvoker(networkManager);
// references
this.networkManager = netManager;
this.wrappedChannel = channel;
this.channelListener = listener;
this.networkManager = networkManager;
this.channel = channel;
this.listenerManager = listenerManager;
this.injectionFactory = injector;
// register us into the channel
this.wrappedChannel.attr(INJECTOR).set(this);
this.channel.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(netManager, true).getField(FuzzyFieldContract.newBuilder()
Field channelField = FuzzyReflection.fromObject(networkManager, true).getField(FuzzyFieldContract.newBuilder()
.typeExact(Channel.class)
.banModifier(Modifier.STATIC)
.build());
@@ -160,135 +140,110 @@ 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.wrappedChannel.closeFuture().addListener(future -> this.close());
this.channel.closeFuture().addListener(future -> this.close());
}
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 SocketAddress getAddress() {
return this.channel.remoteAddress();
}
@Override
public int getProtocolVersion() {
Integer protocolVersion = this.wrappedChannel.attr(PROTOCOL_VERSION).get();
Integer protocolVersion = this.channel.attr(PROTOCOL_VERSION).get();
return protocolVersion == null ? MinecraftProtocolVersion.getCurrentVersion() : protocolVersion;
}
@Override
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;
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;
}
// 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;
}
@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;
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;
}
// remove known references to us
this.wrappedChannel.attr(INJECTOR).remove();
this.channelField.set(this.networkManager, this.wrappedChannel);
// replace wrapped channel in NetworkManager with original channel
this.channelField.set(this.networkManager, this.channel);
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);
// remove all ProtocolLib netty handler
ChannelPipeline pipeline = this.channel.pipeline();
for (String handlerName : NETTY_HANDLER_NAMES) {
if (pipeline.context(handlerName) != null) {
pipeline.remove(handlerName);
}
}
// mark injector as uninjected
this.injected = false;
}
@Override
public void close() {
// ensure that the injector wasn't close before
if (!this.closed) {
this.closed = true;
if (this.closed.compareAndSet(false, 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();
@@ -299,9 +254,9 @@ public class NettyChannelInjector implements Injector {
}
@Override
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) {
public void sendClientboundPacket(Object packet, NetworkMarker marker, boolean filtered) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
@@ -311,19 +266,12 @@ public class NettyChannelInjector implements Injector {
}
// save the given packet marker and send the packet
this.saveMarker(packet, marker);
if (marker != null) {
this.savedMarkers.put(packet, marker);
}
try {
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);
}
}
this.listenerInvoker.send(packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_PACKET)
.messageParam(packet, this.playerName)
@@ -333,89 +281,99 @@ public class NettyChannelInjector implements Injector {
}
@Override
public void receiveClientPacket(Object packet) {
// do not do that if we're not injected or this injector was closed
if (this.closed || !this.injected) {
public void readServerboundPacket(Object packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
this.ensureInEventLoop(() -> {
try {
// try to invoke the method, this should normally not fail
this.listenerInvoker.read(packet);
} catch (Exception exception) {
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_READ_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
});
}
@Override
public void sendWirePacket(WirePacket packet) {
// ignore call if the injector is closed or not injected
if (this.closed.get() || !this.injected) {
return;
}
Runnable receiveAction = () -> {
this.ensureInEventLoop(() -> {
try {
// try to invoke the method, this should normally not fail
MinecraftMethods.getNetworkManagerReadPacketMethod().invoke(this.networkManager, null, packet);
this.channel.writeAndFlush(packet);
} catch (Exception exception) {
// 99% the user gave wrong information to the server
this.errorReporter.reportMinimal(this.injectionFactory.getPlugin(), "receiveClientPacket", exception);
this.errorReporter.reportWarning(this, Report.newBuilder(REPORT_CANNOT_SEND_WRITE_PACKET)
.messageParam(packet, this.playerName)
.error(exception)
.build());
}
};
// 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);
}
});
}
public PacketType.Protocol getInboundProtocol() {
if (this.inboundProtocolReader != null) {
return this.inboundProtocolReader.getProtocol();
@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());
}
return getCurrentProtocol(PacketType.Sender.CLIENT);
}
@Override
public Protocol getCurrentProtocol(PacketType.Sender 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);
}
return ChannelProtocolUtil.PROTOCOL_RESOLVER.apply(this.channel, sender);
}
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.resolvedPlayer != null) {
return this.resolvedPlayer;
if (this.player != null) {
return this.player;
}
// check if the name of the player is already known to the injector
if (this.playerName != null) {
this.resolvedPlayer = this.server.getPlayerExact(this.playerName);
this.player = Bukkit.getPlayerExact(this.playerName);
if (this.player != null) {
this.injectionFactory.cacheInjector(this.player, this);
}
}
// either we resolved it or we didn't...
return this.resolvedPlayer;
return this.player;
}
@Override
public void setPlayer(Player player) {
this.resolvedPlayer = player;
this.injectionFactory.invalidate(this.player, this.playerName);
this.player = player;
this.playerName = player.getName();
this.injectionFactory.cacheInjector(player, this);
this.injectionFactory.cacheInjector(player.getName(), this);
}
@Override
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);
}
}
public boolean isConnected() {
return this.channel.isActive();
}
@Override
@@ -425,139 +383,97 @@ public class NettyChannelInjector implements Injector {
@Override
public boolean isClosed() {
return this.closed;
return this.closed.get();
}
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())) {
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;
if (MinecraftVersion.WILD_UPDATE.atOrAbove()) {
// 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);
}
// 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;
}
// get the username from the packet
String username = (String) LOGIN_PROFILE_ACCESSOR.get(packet);
// cache the injector and the player name
if (username != null) {
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);
}
return;
}
// 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;
}
}
// 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);
}
}
}
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();
// invoke plugin listeners
if (this.listenerManager.hasInboundListener(packet.getType())) {
this.processInboundInternal(ctx, packet);
} else {
eventLoop.execute(runnable);
ctx.fireChannelRead(packet.getHandle());
}
}
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));
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;
}
// 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) {
// create event and invoke listeners
PacketEvent event = PacketEvent.fromClient(this, packetContainer, this.player);
this.listenerManager.invokeInboundPacketListeners(event);
// get packet of event
Object packet = event.getPacket().getHandle();
// fire the intercepted packet down the pipeline if it wasn't cancelled and isn't null
if (!event.isCancelled() && packet != 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(interceptionResult);
NetworkMarker marker = NetworkMarker.getNetworkMarker(event);
if (marker != null) {
this.networkProcessor.invokePostEvent(interceptionResult, marker);
this.networkProcessor.invokePostEvent(event, marker);
}
}
}
<T> T processOutbound(T action) {
// 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
// try to get packet field accessor or return on failure
FieldAccessor packetAccessor = this.lookupPacketAccessor(action);
if (packetAccessor == NO_OP_ACCESSOR) {
if (packetAccessor == FieldAccessor.NO_OP_ACCESSOR) {
return action;
}
@@ -583,38 +499,40 @@ 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());
return action;
}
PacketType packetType = PacketRegistry.getPacketType(protocol, packet.getClass());
if (packetType == null) {
ProtocolLogger.debug("skipping unknown outbound packet type for {0}", packet.getClass());
return action;
}
// no listener and no marker - no magic :)
if (!this.channelListener.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
if (!this.listenerManager.hasOutboundListener(packetType) && marker == null && !MinecraftReflection.isBundlePacket(packet.getClass())) {
return action;
}
// ensure that we are on the main thread if we need to
if (this.channelListener.hasMainThreadListener(packetType) && !this.server.isPrimaryThread()) {
if (this.listenerManager.hasMainThreadListener(packetType) && !Bukkit.isPrimaryThread()) {
// not on the main thread but we are required to be - re-schedule the packet on the main thread
ProtocolLibrary.getScheduler().runTask(
() -> this.sendServerPacket(packet, null, true));
ProtocolLibrary.getScheduler().runTask(() -> this.sendClientboundPacket(packet, null, true));
return null;
}
// call all listeners which are listening to the outbound packet, if any
// null indicates that no listener was affected by the packet, meaning that we can directly send the original packet
// create event and invoke listeners
PacketContainer packetContainer = new PacketContainer(packetType, packet);
PacketEvent event = this.channelListener.onPacketSending(this, packetContainer, marker);
if (event == null) {
return action;
}
PacketEvent event = PacketEvent.fromServer(this, packetContainer, marker, this.player);
this.listenerManager.invokeOutboundPacketListeners(event);
// get packet of event
Object interceptedPacket = event.getPacket().getHandle();
// if the event wasn't cancelled by this action we must recheck if the packet changed during the method call
if (!event.isCancelled()) {
if (!event.isCancelled() && interceptedPacket != null) {
// 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);
}
@@ -664,37 +582,29 @@ public class NettyChannelInjector implements Injector {
}
private FieldAccessor lookupPacketAccessor(Object action) {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), clazz -> {
return PACKET_ACCESSORS.computeIfAbsent(action.getClass(), key -> {
try {
// find the field
Field packetField = FuzzyReflection.fromClass(clazz, true).getField(FuzzyFieldContract
.newBuilder()
Field packetField = FuzzyReflection.fromClass(key, true).getField(
FuzzyFieldContract.newBuilder()
.typeSuperOf(MinecraftReflection.getPacketClass())
.build());
return Accessors.getFieldAccessor(packetField);
} catch (IllegalArgumentException exception) {
// no such field found :(
return NO_OP_ACCESSOR;
// return noop accessor because computeIfAbsent interprets null as a missing key
return FieldAccessor.NO_OP_ACCESSOR;
}
});
}
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(Runnable runnable) {
this.ensureInEventLoop(this.channel.eventLoop(), runnable);
}
public Channel getWrappedChannel() {
return this.wrappedChannel;
private void ensureInEventLoop(EventLoop eventLoop, Runnable runnable) {
if (eventLoop.inEventLoop()) {
runnable.run();
} else {
eventLoop.execute(runnable);
}
}
}
@@ -1,44 +0,0 @@
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,5 +1,15 @@
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;
@@ -10,21 +20,12 @@ 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.
*/
abstract class NettyEventLoopProxy implements EventLoop {
final class NettyEventLoopProxy implements EventLoop {
private static final Callable<?> EMPTY_CALLABLE = () -> null;
private static final Runnable EMPTY_RUNNABLE = () -> {
@@ -40,7 +41,7 @@ abstract class NettyEventLoopProxy implements EventLoop {
private Runnable proxyRunnable(Runnable original) {
// execute the proxy and check if we need to do anything
Runnable proxied = this.doProxyRunnable(original);
Runnable proxied = this.injector.processOutbound(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -55,7 +56,7 @@ abstract 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.doProxyCallable(original);
Callable<T> proxied = this.injector.processOutbound(original);
if (proxied != null && proxied == original) {
// was not changed, we need to mark the packet as processed manually
return () -> {
@@ -68,26 +69,6 @@ abstract 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();
@@ -0,0 +1,237 @@
package com.comphenix.protocol.injector.netty.channel;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import io.netty.channel.ChannelHandlerContext;
/**
* This class facilitates the invocation of methods on the current packet listener.
* It attempts to execute the <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,9 +1,10 @@
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;
@@ -11,18 +12,15 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
private static final ReportType CANNOT_INJECT_CHANNEL = new ReportType("Unable to inject incoming channel %s.");
private final InjectionFactory factory;
private final NetworkManagerInjector listener;
private final TemporaryPlayerFactory playerFactory;
private final ErrorReporter errorReporter;
private final InjectionFactory injectionFactory;
public InjectionChannelInboundHandler(
InjectionFactory factory,
NetworkManagerInjector listener,
TemporaryPlayerFactory playerFactory
ErrorReporter errorReporter,
InjectionFactory injectionFactory
) {
this.factory = factory;
this.listener = listener;
this.playerFactory = playerFactory;
this.errorReporter = errorReporter;
this.injectionFactory = injectionFactory;
}
@Override
@@ -35,11 +33,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.factory.isClosed()) {
if (!this.injectionFactory.isClosed()) {
try {
this.factory.fromChannel(ctx.channel(), this.listener, this.playerFactory).inject();
this.injectionFactory.fromChannel(ctx.channel()).inject();
} catch (Exception exception) {
this.listener.getReporter().reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
this.errorReporter.reportDetailed(this, Report.newBuilder(CANNOT_INJECT_CHANNEL)
.messageParam(ctx.channel())
.error(exception)
.build());
@@ -52,7 +50,7 @@ final class InjectionChannelInboundHandler extends ChannelInboundHandlerAdapter
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -28,7 +28,7 @@ final class InjectionChannelInitializer extends ChannelInboundHandlerAdapter {
@Override
public boolean isSharable() {
// we do it this way to prevent the lookup overheat
// we do it this way to prevent the lookup overhead
return true;
}
}
@@ -7,21 +7,14 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.ListenerInvoker;
import com.comphenix.protocol.injector.netty.ChannelListener;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.channel.InjectionFactory;
import com.comphenix.protocol.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;
@@ -32,105 +25,36 @@ import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.ChannelFuture;
public class NetworkManagerInjector implements ChannelListener {
public class NetworkManagerInjector {
private static final String INBOUND_INJECT_HANDLER_NAME = "protocol_lib_inbound_inject";
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;
// injectors based on this "global" injector
private final PlayerInjectionHandler playerInjectionHandler;
// netty handler
private final InjectionChannelInitializer pipelineInjectorHandler;
private boolean debug = false;
// status of this injector
private boolean closed = false;
private boolean injected = false;
public NetworkManagerInjector(Plugin plugin, Server server, ListenerInvoker listenerInvoker, ErrorReporter reporter) {
public NetworkManagerInjector(Plugin plugin, ListenerManager listenerManager, ErrorReporter reporter) {
this.errorReporter = reporter;
this.listenerInvoker = listenerInvoker;
this.injectionFactory = new InjectionFactory(plugin, server, reporter);
this.injectionFactory = new InjectionFactory(plugin, reporter, listenerManager);
// hooking netty handlers
InjectionChannelInboundHandler injectionHandler = new InjectionChannelInboundHandler(
this.injectionFactory,
this,
PLAYER_FACTORY);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
// other injectors
this.playerInjectionHandler = new NetworkManagerPlayerInjector(
this,
this.errorReporter,
this.injectionFactory);
this.pipelineInjectorHandler = new InjectionChannelInitializer(INBOUND_INJECT_HANDLER_NAME, injectionHandler);
}
@Override
public PacketEvent onPacketSending(Injector injector, PacketContainer packet, NetworkMarker marker) {
// check if we need to intercept the packet
Class<?> packetClass = packet.getHandle().getClass();
if (marker != null || MinecraftReflection.isBundlePacket(packetClass) || hasOutboundListener(packet.getType())) {
// wrap packet and construct the event
PacketEvent packetEvent = PacketEvent.fromServer(this, packet, marker, injector.getPlayer());
// post to all listeners, then return the packet event we constructed
this.listenerInvoker.invokePacketSending(packetEvent);
return packetEvent;
}
// no listener so there is no change we need to apply
return null;
}
@Override
public PacketEvent onPacketReceiving(Injector injector, PacketContainer packet, NetworkMarker marker) {
if (marker != null || hasInboundListener(packet.getType())) {
PacketEvent packetEvent = PacketEvent.fromClient(this, packet, marker, injector.getPlayer());
// post to all listeners, then return the packet event we constructed
this.listenerInvoker.invokePacketReceiving(packetEvent);
return packetEvent;
}
// no listener so there is no change we need to apply
return null;
}
@Override
public boolean hasInboundListener(PacketType packetType) {
return this.listenerInvoker.hasInboundListener(packetType);
}
@Override
public boolean hasOutboundListener(PacketType packetType) {
return this.listenerInvoker.hasOutboundListener(packetType);
}
@Override
public boolean hasMainThreadListener(PacketType packetType) {
return this.listenerInvoker.hasMainThreadListener(packetType);
}
@Override
public ErrorReporter getReporter() {
return this.errorReporter;
}
@Override
public boolean isDebug() {
return this.debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
public Injector getInjector(Player player) {
return this.injectionFactory.fromPlayer(player);
}
@SuppressWarnings("unchecked")
@@ -231,8 +155,4 @@ public class NetworkManagerInjector implements ChannelListener {
this.overriddenLists.clear();
this.injectionFactory.close();
}
public PlayerInjectionHandler getPlayerInjectionHandler() {
return this.playerInjectionHandler;
}
}
@@ -1,68 +0,0 @@
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;
}
}
@@ -1,81 +0,0 @@
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;
}
}
@@ -1,50 +0,0 @@
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,5 +1,9 @@
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>
@@ -8,15 +12,17 @@ package com.comphenix.protocol.injector.temporary;
*/
public class TemporaryPlayer {
private volatile MinimalInjector injector;
protected volatile Injector injector;
MinimalInjector getInjector() {
public Injector getInjector() {
return this.injector;
}
void setInjector(MinimalInjector injector) {
if (injector == null) {
throw new IllegalArgumentException("Injector cannot be NULL.");
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");
}
this.injector = injector;
@@ -17,17 +17,20 @@
package com.comphenix.protocol.injector.temporary;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
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 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;
@@ -36,8 +39,6 @@ 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.
@@ -46,13 +47,15 @@ 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 MinimalInjector getInjectorFromPlayer(Player player) {
public static Injector getInjectorFromPlayer(Player player) {
if (player instanceof TemporaryPlayer) {
return ((TemporaryPlayer) player).getInjector();
}
@@ -65,7 +68,7 @@ public class TemporaryPlayerFactory {
* @param player - the player to update.
* @param injector - the injector to store.
*/
public static void setInjectorInPlayer(Player player, MinimalInjector injector) {
public static void setInjectorForPlayer(Player player, Injector injector) {
((TemporaryPlayer) player).setInjector(injector);
}
@@ -76,11 +79,10 @@ public class TemporaryPlayerFactory {
public Object delegate(
@This Object obj,
@Origin Method method,
@FieldValue("server") Server server,
@FieldValue("injector") Injector injector,
@AllArguments Object... args
) throws Throwable {
String methodName = method.getName();
MinimalInjector injector = ((TemporaryPlayer) obj).getInjector();
if (injector == null) {
throw new IllegalStateException("Unable to find injector.");
@@ -92,7 +94,7 @@ public class TemporaryPlayerFactory {
} else if (methodName.equals("getAddress")) {
return injector.getAddress();
} else if (methodName.equals("getServer")) {
return server;
return Bukkit.getServer();
}
// Handle send message methods
@@ -144,21 +146,15 @@ public class TemporaryPlayerFactory {
try {
final Constructor<?> constructor = ByteBuddyFactory.getInstance()
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.NO_CONSTRUCTORS)
.createSubclass(TemporaryPlayer.class, ConstructorStrategy.Default.DEFAULT_CONSTRUCTOR)
.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(Server.class);
.getDeclaredConstructor();
return (Constructor<? extends Player>) constructor;
} catch (NoSuchMethodException e) {
throw new RuntimeException("Failed to find Temporary Player constructor!", e);
@@ -172,9 +168,9 @@ public class TemporaryPlayerFactory {
* @param message - a message.
* @return Always NULL.
*/
private static Object sendMessage(MinimalInjector injector, String message) {
private static Object sendMessage(Injector injector, String message) {
for (PacketContainer packet : ChatExtensions.createChatPackets(message)) {
injector.sendServerPacket(packet.getHandle(), null, false);
injector.sendClientboundPacket(packet.getHandle(), null, false);
}
return null;
@@ -197,27 +193,13 @@ 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 Player createTemporaryPlayer(final Server server) {
public static Player createTemporaryPlayer() {
try {
return PLAYER_CONSTRUCTOR.newInstance(server);
return PLAYER_CONSTRUCTOR.newInstance();
} 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,6 +4,21 @@ 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,6 +9,25 @@ 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,16 +1,18 @@
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 {
@@ -58,14 +60,20 @@ public class MinecraftGenerator {
try {
String name = type.getName();
if (name.contains("it.unimi.dsi.fastutil")) {
Class<?> clz = Class.forName(name.replace("Map", "OpenHashMap"));
return Accessors.getConstructorAccessorOrNull(clz);
// convert interface maps to OpenHashMaps
if (!name.endsWith("OpenHashMap")) {
name = name.replace("Map", "OpenHashMap");
}
Class<?> hashMapClass = Class.forName(name);
return Accessors.getConstructorAccessorOrNull(hashMapClass);
}
} catch (Exception ignored) {
}
return null;
return ConstructorAccessor.NO_OP_ACCESSOR;
});
if (ctor != null) {
if (ctor != ConstructorAccessor.NO_OP_ACCESSOR) {
try {
return ctor.invoke();
} catch (Exception ignored) {
@@ -35,12 +35,13 @@ import net.bytebuddy.matcher.ElementMatchers;
public final class MinecraftMethods {
// For player connection
private volatile static MethodAccessor sendPacketMethod;
private volatile static MethodAccessor disconnectMethod;
private volatile static MethodAccessor playerConnectionSendMethod;
private volatile static MethodAccessor playerConnectionDisconnectMethod;
// For network manager
private volatile static MethodAccessor networkManagerHandle;
private volatile static MethodAccessor networkManagerSend;
private volatile static MethodAccessor networkManagerPacketRead;
private volatile static MethodAccessor networkManagerDisconnect;
// For packet
private volatile static MethodAccessor packetReadByteBuf;
@@ -78,18 +79,18 @@ public final class MinecraftMethods {
*
* @return The send packet method.
*/
public static MethodAccessor getSendPacketMethod() {
if (sendPacketMethod == null) {
public static MethodAccessor getPlayerConnectionSendMethod() {
if (playerConnectionSendMethod == null) {
FuzzyReflection serverHandlerClass = FuzzyReflection.fromClass(MinecraftReflection.getPlayerConnectionClass());
try {
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(1)
.returnTypeVoid()
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build()));
} catch (IllegalArgumentException e) {
sendPacketMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
playerConnectionSendMethod = Accessors.getMethodAccessor(serverHandlerClass.getMethod(FuzzyMethodContract.newBuilder()
.nameRegex("sendPacket.*")
.returnTypeVoid()
.parameterCount(1)
@@ -97,33 +98,41 @@ public final class MinecraftMethods {
}
}
return sendPacketMethod;
return playerConnectionSendMethod;
}
/**
* Retrieve the disconnect method for a given player connection.
*
* @param playerConnection - the player connection.
* @return The
*/
public static MethodAccessor getDisconnectMethod(Class<?> playerConnection) {
if (disconnectMethod == null) {
FuzzyReflection playerConnectionClass = FuzzyReflection.fromClass(playerConnection);
try {
disconnectMethod = Accessors.getMethodAccessor(playerConnectionClass.getMethod(FuzzyMethodContract.newBuilder()
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()
.returnTypeVoid()
.nameRegex("disconnect.*")
.parameterCount(1)
.parameterExactType(String.class, 0)
.parameterExactType(MinecraftReflection.getIChatBaseComponentClass(), 0)
.build()));
} catch (IllegalArgumentException e) {
// Just assume it's the first String method
Method disconnect = playerConnectionClass.getMethodByParameters("disconnect", String.class);
disconnectMethod = Accessors.getMethodAccessor(disconnect);
} 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);
}
}
}
return disconnectMethod;
return playerConnectionDisconnectMethod;
}
/**
@@ -131,8 +140,8 @@ public final class MinecraftMethods {
*
* @return The handle method.
*/
public static MethodAccessor getNetworkManagerHandleMethod() {
if (networkManagerHandle == null) {
public static MethodAccessor getNetworkManagerSendMethod() {
if (networkManagerSend == null) {
Method handleMethod = FuzzyReflection
.fromClass(MinecraftReflection.getNetworkManagerClass(), true)
.getMethod(FuzzyMethodContract.newBuilder()
@@ -141,10 +150,10 @@ public final class MinecraftMethods {
.parameterCount(1)
.parameterExactType(MinecraftReflection.getPacketClass(), 0)
.build());
networkManagerHandle = Accessors.getMethodAccessor(handleMethod);
networkManagerSend = Accessors.getMethodAccessor(handleMethod);
}
return networkManagerHandle;
return networkManagerSend;
}
/**
@@ -163,6 +172,27 @@ 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,6 +373,11 @@ 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());
}
@@ -502,6 +507,10 @@ public final class MinecraftReflection {
return is(getCraftItemStackClass(), obj);
}
public static boolean isIChatBaseComponent(Class<?> target) {
return is(getIChatBaseComponentClass(), target);
}
/**
* Retrieve the EntityPlayer (NMS) class.
*
@@ -10,7 +10,9 @@ import java.util.Base64;
import com.comphenix.protocol.injector.netty.NettyByteBufAdapter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.reflect.fuzzy.FuzzyFieldContract;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.comphenix.protocol.wrappers.nbt.NbtFactory;
@@ -31,6 +33,7 @@ public class StreamSerializer {
private static final StreamSerializer DEFAULT = new StreamSerializer();
// Cached methods
private static FieldAccessor STREAM_CODEC;
private static MethodAccessor READ_ITEM_METHOD;
private static MethodAccessor WRITE_ITEM_METHOD;
@@ -235,14 +238,35 @@ public class StreamSerializer {
Object serializer = MinecraftReflection.getPacketDataSerializer(buf);
if (READ_ITEM_METHOD == null) {
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(serializer.getClass(), false)
.getMethodByReturnTypeAndParameters("readItemStack", MinecraftReflection.getItemStackClass()));
if (MinecraftVersion.v1_21_0.atOrAbove()) {
if (STREAM_CODEC == null) {
STREAM_CODEC = Accessors.getFieldAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getItemStackClass())
.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getStreamCodecClass())
.build()).get(1)); //skip OPTIONAL_STREAM_CODEC
}
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromObject(STREAM_CODEC.get(null), true)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactType(MinecraftReflection.getRegistryFriendlyByteBufClass().get())
.returnTypeExact(MinecraftReflection.getItemStackClass())
.build()));
} else {
READ_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(serializer.getClass(), false)
.getMethodByReturnTypeAndParameters("readItemStack", MinecraftReflection.getItemStackClass()));
}
}
try {
// unwrap the item
Object nmsItem = READ_ITEM_METHOD.invoke(serializer);
Object nmsItem;
if (MinecraftVersion.v1_21_0.atOrAbove()) {
nmsItem = READ_ITEM_METHOD.invoke(STREAM_CODEC.get(null), serializer);
} else {
nmsItem = READ_ITEM_METHOD.invoke(serializer);
}
return nmsItem != null ? MinecraftReflection.getBukkitItemStack(nmsItem) : null;
} finally {
ReferenceCountUtil.safeRelease(buf);
@@ -262,11 +286,26 @@ public class StreamSerializer {
* @throws IOException If the operation fails due to reflection problems.
*/
public void serializeItemStack(DataOutputStream output, ItemStack stack) throws IOException {
// TODO this functionality was replaced by the CODEC field in the nms ItemStack
if (WRITE_ITEM_METHOD == null) {
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true)
.getMethodByParameters("writeStack", MinecraftReflection.getItemStackClass()));
if (MinecraftVersion.v1_21_0.atOrAbove()) {
if (STREAM_CODEC == null) {
STREAM_CODEC = Accessors.getFieldAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getItemStackClass())
.getFieldList(FuzzyFieldContract.newBuilder()
.typeExact(MinecraftReflection.getStreamCodecClass())
.build()).get(1)); //skip OPTIONAL_STREAM_CODEC
}
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection.fromObject(STREAM_CODEC.get(null), true)
.getMethod(FuzzyMethodContract.newBuilder()
.parameterExactArray(MinecraftReflection.getRegistryFriendlyByteBufClass().get(), MinecraftReflection.getItemStackClass())
.returnTypeExact(void.class)
.build()));
} else {
WRITE_ITEM_METHOD = Accessors.getMethodAccessor(FuzzyReflection
.fromClass(MinecraftReflection.getPacketDataSerializerClass(), true)
.getMethodByParameters("writeStack", MinecraftReflection.getItemStackClass()));
}
}
ByteBuf buf = Unpooled.buffer();
@@ -274,7 +313,12 @@ public class StreamSerializer {
// Get the NMS version of the ItemStack and write it into the buffer
Object nmsItem = MinecraftReflection.getMinecraftItemStack(stack);
WRITE_ITEM_METHOD.invoke(serializer, nmsItem);
if (MinecraftVersion.v1_21_0.atOrAbove()) {
WRITE_ITEM_METHOD.invoke(STREAM_CODEC.get(null), serializer, nmsItem);
} else {
WRITE_ITEM_METHOD.invoke(serializer, nmsItem);
}
// write the serialized content to the stream
output.write(this.getBytesAndRelease(buf));
@@ -86,6 +86,15 @@ 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,6 +11,10 @@ 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;
@@ -23,10 +27,6 @@ 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,6 +623,21 @@ 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;
@@ -647,7 +662,9 @@ 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<>();
@@ -660,116 +677,129 @@ public abstract class EnumWrappers {
if (INITIALIZED)
return;
INITIALIZED = true;
synchronized (EnumWrappers.class) {
// Recheck initialization status inside the lock
if (INITIALIZING || INITIALIZED)
return;
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.getPacketClass(), 0);
// Prevent circular calls to initialize during initialization
// (certain methods below indirectly call initialize again)
INITIALIZING = true;
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);
}
PROTOCOL_CLASS = MinecraftReflection.getEnumProtocolClass();
CLIENT_COMMAND_CLASS = getEnum(PacketType.Play.Client.CLIENT_COMMAND.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);
}
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()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.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);
}
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);
if (MinecraftVersion.CONFIG_PHASE_PROTOCOL_UPDATE.atOrAbove()) {
GAMEMODE_CLASS = getEnum(MinecraftReflection.getPlayerInfoDataClass(), 0);
} else {
GAMEMODE_CLASS = getEnum(PacketType.Play.Server.LOGIN.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];
}
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);
try {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.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];
}
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 {
SOUND_CATEGORY_CLASS = MinecraftReflection.getMinecraftClass("sounds.SoundCategory");
} catch (Exception ex) {
SOUND_CATEGORY_CLASS = getEnum(PacketType.Play.Server.NAMED_SOUND_EFFECT.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");
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);
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
// 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");
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);
}
FuzzyReflection fuzzy = FuzzyReflection.fromClass(MinecraftReflection.getEnumEntityUseActionClass(), true);
Method getType = fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeMatches(FuzzyMatchers.except(Void.class))
.build());
// 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);
}
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);
}
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");
// 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);
}
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
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");
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());
RENDER_TYPE_CLASS = MinecraftReflection.getNullableNMS(
"world.scores.criteria.ObjectiveCriteria$RenderType",
"world.scores.criteria.IScoreboardCriteria$EnumScoreboardHealthDisplay",
"IScoreboardCriteria$EnumScoreboardHealthDisplay");
CHAT_FORMATTING_CLASS = MinecraftReflection.getNullableNMS("ChatFormatting", "EnumChatFormat");
if (ENTITY_POSE_CLASS != null) {
associate(ENTITY_POSE_CLASS, EntityPose.class, getEntityPoseConverter());
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;
}
}
@@ -933,6 +963,11 @@ 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);
@@ -1026,6 +1061,10 @@ 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.getDisplayName();
this.suffix = template.getDisplayName();
this.prefix = template.getPrefix();
this.suffix = template.getSuffix();
this.nametagVisibility = template.getNametagVisibility();
this.collisionRule = template.getCollisionRule();
this.color = template.getColor();
@@ -1,19 +1,25 @@
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 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.core.LayeredRegistryAccess;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.core.registries.Registries;
import net.minecraft.resources.MinecraftKey;
import net.minecraft.resources.RegistryDataLoader;
import net.minecraft.server.DataPackResources;
import net.minecraft.server.DispenserRegistry;
@@ -26,20 +32,32 @@ 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.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.jetbrains.annotations.NotNull;
import org.spigotmc.SpigotWorldConfig;
import static org.mockito.ArgumentMatchers.any;
@@ -104,7 +122,7 @@ public class BukkitInitialization {
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().d(); // .compositeAccess().freeze()
// IRegistryCustom.Dimension registryCustom = layeredRegistryAccess.a().c(); // .compositeAccess().freeze()
DataPackResources dataPackResources = DataPackResources.a(
@@ -148,7 +166,54 @@ public class BukkitInitialization {
});
when(mockedServer.getRegistry(any())).thenAnswer(invocation -> {
Class<Keyed> registryType = invocation.getArgument(0);
return CraftRegistry.createRegistry(registryType, registryCustom);
Object registry = CraftRegistry.createRegistry(registryType, registryCustom);
if (registry == null) {
System.err.println("WARN: Missing registry for " + registryType);
return new DummyRegistry<>();
}
return registry;
});
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();
}
return null;
});
WorldServer nmsWorld = mock(WorldServer.class);
@@ -196,4 +261,28 @@ 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 T getOrThrow(@NotNull NamespacedKey namespacedKey) {
return null;
}
@Override
public Stream<T> stream() {
List<T> empty = Collections.emptyList();
return empty.stream();
}
}
}
@@ -1,53 +0,0 @@
/*
* ProtocolLib - Bukkit server library that allows access to the Minecraft protocol.
* Copyright (C) 2012 Kristian S. Stangeland
*
* This program is free software; you can redistribute it and/or modify it under the terms of the
* GNU General Public License as published by the Free Software Foundation; either version 2 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along with this program;
* if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
* 02111-1307 USA
*/
package com.comphenix.protocol.concurrency;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.junit.jupiter.api.Test;
public class BlockingHashMapTest {
@Test
public void test() throws InterruptedException, ExecutionException {
final BlockingHashMap<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");
}
}
@@ -0,0 +1,21 @@
package com.comphenix.protocol.injector.netty.channel;
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.BukkitInitialization;
public class PacketListenerInvokerTest {
@BeforeAll
public static void beforeClass() {
BukkitInitialization.initializeAll();
}
@Test
public void testInitialization() {
assertDoesNotThrow(() -> PacketListenerInvoker.ensureStaticInitializedWithoutError());
}
}
@@ -1,6 +1,5 @@
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;
@@ -12,12 +11,8 @@ import org.mockito.MockitoAnnotations;
public class TemporaryPlayerFactoryTest {
private static final TemporaryPlayerFactory temporaryPlayerFactory = new TemporaryPlayerFactory();
@Mock
Server server;
@Mock
MinimalInjector minimalInjector;
@BeforeEach
public void initMocks() {
@@ -26,17 +21,7 @@ public class TemporaryPlayerFactoryTest {
@Test
public void testUnavailableSocketInjector() {
Player player = temporaryPlayerFactory.createTemporaryPlayer(this.server);
Player player = TemporaryPlayerFactory.createTemporaryPlayer();
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);
}
}
@@ -0,0 +1,36 @@
package com.comphenix.protocol.reflect.instances;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import java.util.UUID;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import com.comphenix.protocol.BukkitInitialization;
import com.comphenix.protocol.PacketType;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import net.minecraft.core.NonNullList;
import net.minecraft.world.entity.EntityTypes;
import net.minecraft.world.item.ItemStack;
public class MinecraftGeneratorTest {
@BeforeAll
public static void beforeClass() {
BukkitInitialization.initializeAll();
}
@Test
public void testInstantiation() {
assertNotNull(MinecraftGenerator.INSTANCE.create(UUID.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(PacketType.Protocol.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(ItemStack.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(EntityTypes.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(Int2ObjectMap.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(Int2ObjectOpenHashMap.class));
assertNotNull(MinecraftGenerator.INSTANCE.create(NonNullList.class));
}
}
@@ -2,11 +2,13 @@ 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
@@ -16,8 +18,12 @@ public class MinecraftMethodsTest {
@Test
public void testSendPacketMethods() {
assertNotNull(MinecraftMethods.getSendPacketMethod());
assertNotNull(MinecraftMethods.getNetworkManagerHandleMethod());
assertNotNull(MinecraftMethods.getPlayerConnectionSendMethod());
assertNotNull(MinecraftMethods.getPlayerConnectionDisconnectMethod());
assertNotNull(MinecraftMethods.getNetworkManagerSendMethod());
assertNotNull(MinecraftMethods.getNetworkManagerReadPacketMethod());
assertNotNull(MinecraftMethods.getNetworkManagerDisconnectMethod());
}
private void setNull(final String fieldName) throws NoSuchFieldException, IllegalAccessException {
@@ -60,7 +60,6 @@ public class StreamSerializerTest {
}
@Test
@Disabled // TODO -- replaced with registry friendly bytebuf
public void testItems() throws IOException {
StreamSerializer serializer = new StreamSerializer();
ItemStack initial = new ItemStack(Material.STRING);
@@ -72,7 +71,6 @@ public class StreamSerializerTest {
}
@Test
@Disabled // TODO -- replaced with registry friendly bytebuf
public void testItemMeta() throws IOException {
StreamSerializer serializer = new StreamSerializer();
ItemStack initial = new ItemStack(Material.BLUE_WOOL, 2);