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