Compare commits

...
1 Commits
Author SHA1 Message Date
Dan Mulloy 39808bc64f wip: dedicated paper plugin 2025-08-21 21:34:27 -05:00
40 changed files with 1030 additions and 388 deletions

No files matched your search

+34 -29
View File
@@ -1,5 +1,6 @@
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
import io.github.patrick.gradle.remapper.RemapTask
//import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar
//import io.github.patrick.gradle.remapper.RemapTask
import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar.Companion.shadowJar
import org.gradle.api.tasks.testing.logging.TestExceptionFormat
plugins {
@@ -7,8 +8,9 @@ plugins {
`maven-publish`
`signing`
id("com.gradleup.shadow") version "9.0.2"
id("io.github.patrick.remapper") version "1.4.2"
// id("io.github.patrick.remapper") version "1.4.2"
id("com.vanniktech.maven.publish") version "0.34.0"
id("io.papermc.paperweight.userdev") version "2.0.0-beta.18"
}
group = "net.dmulloy2"
@@ -19,6 +21,18 @@ val isSnapshot = version.toString().endsWith("-SNAPSHOT")
val commitHash = System.getenv("COMMIT_SHA") ?: ""
val isCI = commitHash.isNotEmpty()
paperweight {
reobfArtifactConfiguration = io.papermc.paperweight.userdev.ReobfArtifactConfiguration.REOBF_PRODUCTION
}
tasks.assemble {
dependsOn(tasks.reobfJar)
}
/*tasks.shadowJar {
dependsOn(tasks.assemble)
}*/
repositories {
if (!isCI) {
mavenLocal()
@@ -46,31 +60,30 @@ repositories {
dependencies {
implementation("net.bytebuddy:byte-buddy:1.17.5")
compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
// compileOnly("org.spigotmc:spigot-api:${mcVersion}-R0.1-SNAPSHOT")
// compileOnly("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
compileOnly("io.netty:netty-all:4.0.23.Final")
/*
* TODO(fix): once you update kyori:adventure-text-serializer-gson please uncomment the TODO in
* com.comphenix.protocol.wrappers.WrappedComponentStyleTest if the following issue got fixed:
* https://github.com/KyoriPowered/adventure/issues/1194
*/
compileOnly("net.kyori:adventure-text-serializer-gson:4.21.0")
compileOnly("net.kyori:adventure-text-serializer-gson:4.24.0")
compileOnly("net.kyori:adventure-text-serializer-ansi:4.24.0")
compileOnly("com.googlecode.json-simple:json-simple:1.1.1")
paperweight.paperDevBundle("1.21.8-R0.1-SNAPSHOT")
testImplementation("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.10.0")
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:${mcVersion}-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")
// testImplementation("org.spigotmc:spigot:${mcVersion}-R0.1-SNAPSHOT:remapped-mojang")
testImplementation("net.kyori:adventure-text-serializer-gson:4.24.0")
testImplementation("net.kyori:adventure-text-serializer-plain:4.24.0")
testImplementation("net.kyori:adventure-text-serializer-ansi:4.24.0")
}
java {
sourceCompatibility = JavaVersion.VERSION_17
targetCompatibility = JavaVersion.VERSION_17
sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21
}
tasks {
@@ -95,25 +108,17 @@ tasks {
}
relocate("net.bytebuddy", "com.comphenix.net.bytebuddy")
manifest {
/*manifest {
attributes(
"paperweight-mappings-namespace" to "mojang"
)
}
}*/
archiveFileName = "ProtocolLib.jar"
// archiveFileName = "ProtocolLib.jar"
}
remap {
dependsOn("shadowJar")
inputTask.set(getByName<ShadowJar>("shadowJar"))
version.set(mcVersion)
action.set(RemapTask.Action.MOJANG_TO_SPIGOT)
}
assemble {
dependsOn("remap")
reobfJar {
outputJar = layout.buildDirectory.file("libs/ProtocolLib.jar")
}
javadoc {
@@ -0,0 +1,38 @@
package com.comphenix.protocol;
import com.comphenix.protocol.injector.PacketFilterManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
class BukkitListener implements Listener {
private final PacketFilterManager manager;
BukkitListener(PacketFilterManager manager) {
this.manager = manager;
}
// @EventHandler(priority = EventPriority.LOWEST)
// public void handleLogin(PlayerLoginEvent event) {
// networkManagerInjector.getInjector(event.getPlayer()).inject();
// }
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
manager.injectPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void handleQuit(PlayerQuitEvent event) {
manager.removePlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void handlePluginUnload(PluginDisableEvent event) {
manager.removePacketListeners(event.getPlugin());
}
}
@@ -21,21 +21,19 @@ import java.util.ArrayDeque;
import java.util.Arrays;
import java.util.Deque;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
/**
* Base class for all our commands.
*
* @author Kristian
*/
abstract class CommandBase implements CommandExecutor {
abstract class CommandBase {
public static final ReportType REPORT_COMMAND_ERROR = new ReportType("Cannot execute command %s.");
public static final ReportType REPORT_UNEXPECTED_COMMAND = new ReportType("Incorrect command assigned to %s.");
@@ -58,15 +56,8 @@ abstract class CommandBase implements CommandExecutor {
this.minimumArgumentCount = minimumArgumentCount;
}
@Override
public final boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean onCommand(CommandSender sender, String[] args) {
try {
// Make sure we're dealing with the correct command
if (!command.getName().equalsIgnoreCase(name)) {
reporter.reportWarning(this, Report.newBuilder(REPORT_UNEXPECTED_COMMAND).messageParam(this));
return false;
}
if (permission != null && !sender.hasPermission(permission)) {
sender.sendMessage(ChatColor.RED + "You haven't got permission to run this command.");
return true;
@@ -81,7 +72,7 @@ abstract class CommandBase implements CommandExecutor {
}
} catch (Throwable ex) {
reporter.reportDetailed(this,
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, label, args)
Report.newBuilder(REPORT_COMMAND_ERROR).error(ex).messageParam(name).callerParam(sender, name, args)
);
return true;
}
@@ -0,0 +1,22 @@
package com.comphenix.protocol;
import com.comphenix.protocol.injector.PacketFilterManager;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
class LegacyLoginListener implements Listener {
private final PacketFilterManager manager;
LegacyLoginListener(PacketFilterManager manager) {
this.manager = manager;
}
@SuppressWarnings("deprecation")
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
manager.injectPlayer(event.getPlayer());
}
}
@@ -16,22 +16,6 @@
*/
package com.comphenix.protocol;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
@@ -48,11 +32,28 @@ import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.PacketType.Sender;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.events.ListeningWhitelist;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.events.PacketListener;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.plugin.Plugin;
/**
* Logs packets to a given stream
* @author dmulloy2
*/
public class PacketLogging implements CommandExecutor, PacketListener {
public class PacketLogging extends CommandBase implements PacketListener {
public static final String NAME = "packetlog";
private static MethodAccessor HEX_DUMP;
@@ -69,93 +70,94 @@ public class PacketLogging implements CommandExecutor, PacketListener {
private final ProtocolManager manager;
private final Plugin plugin;
PacketLogging(Plugin plugin, ProtocolManager manager) {
PacketLogging(ErrorReporter reporter, Plugin plugin, PacketFilterManager manager) {
super(reporter, CommandBase.PERMISSION_ADMIN, NAME, 2);
this.plugin = plugin;
this.manager = manager;
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
public boolean handleCommand(CommandSender sender, String[] args) {
PacketType type = null;
try {
if (args.length > 2) {
Protocol protocol;
if (args.length <= 2) {
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
return true;
}
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
Protocol protocol;
Sender pSender;
try {
protocol = Protocol.valueOf(args[0].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown protocol " + args[0]);
return true;
}
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
Sender pSender;
try {
try { // Try IDs first
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
try {
pSender = Sender.valueOf(args[1].toUpperCase());
} catch (IllegalArgumentException ex) {
sender.sendMessage(ChatColor.RED + "Unknown sender: " + args[1]);
return true;
}
try {
try { // Try IDs first
int id = Integer.parseInt(args[2]);
type = PacketType.findCurrent(protocol, pSender, id);
} catch (NumberFormatException ex) { // Check packet names
String name = args[2];
for (PacketType packet : PacketType.values()) {
if (packet.getProtocol() == protocol && packet.getSender() == pSender) {
if (packet.name().equalsIgnoreCase(name)) {
type = packet;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break;
}
for (String className : packet.getClassNames()) {
if (className.equalsIgnoreCase(name)) {
type = packet;
break;
}
}
}
}
}
} catch (IllegalArgumentException ex) { // RIP
type = null;
}
} catch (IllegalArgumentException ex) { // RIP
type = null;
}
if (type == null) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
return true;
}
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
if (type == null) {
sender.sendMessage(ChatColor.RED + "Unknown packet: " + args[2]);
return true;
}
sender.sendMessage(ChatColor.RED + "Invalid syntax: /packetlog <protocol> <sender> <packet> [location]");
if (args.length > 3) {
if (args[3].equalsIgnoreCase("console")) {
this.location = LogLocation.CONSOLE;
} else {
this.location = LogLocation.FILE;
}
}
if (pSender == Sender.CLIENT) {
if (receivingTypes.contains(type)) {
receivingTypes.remove(type);
} else {
receivingTypes.add(type);
}
} else {
if (sendingTypes.contains(type)) {
sendingTypes.remove(type);
} else {
sendingTypes.add(type);
}
}
startLogging();
sender.sendMessage(ChatColor.GREEN + "Now logging " + type.getPacketClass().getSimpleName());
return true;
} catch (Throwable ex) {
sender.sendMessage(ChatColor.RED + "Failed to parse command: " + ex);
@@ -14,7 +14,7 @@ import java.util.Map;
import java.util.UUID;
import java.util.function.BiConsumer;
import org.apache.commons.lang.WordUtils;
import com.comphenix.protocol.utility.WordUtils;
import org.bukkit.Bukkit;
import com.comphenix.protocol.PacketTypeLookup.ClassLookup;
@@ -0,0 +1,57 @@
package com.comphenix.protocol;
import java.lang.reflect.Field;
import java.util.logging.Level;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import io.netty.channel.Channel;
import io.papermc.paper.connection.PlayerConnection;
import io.papermc.paper.connection.ReadablePlayerCookieConnectionImpl;
import io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
class PaperLoginListener implements Listener {
private final PacketFilterManager manager;
PaperLoginListener(PacketFilterManager manager) {
this.manager = manager;
}
private static FieldAccessor networkManagerField;
private static FieldAccessor channelField;
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerConnectionValidateLogin(PlayerConnectionValidateLoginEvent event) {
try {
PlayerConnection connection = event.getConnection();
ProtocolLogger.log("onPlayerConnectionValidateLogin: " + connection.getAddress());
if (networkManagerField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromClass(ReadablePlayerCookieConnectionImpl.class, true);
Field field = fuzzy.getFieldByType("networkManager", MinecraftReflection.getNetworkManagerClass());
networkManagerField = Accessors.getFieldAccessor(field);
}
Object networkManager = networkManagerField.get(connection);
if (channelField == null) {
FuzzyReflection fuzzy = FuzzyReflection.fromObject(networkManager, true);
Field field = fuzzy.getFieldByType("channel", Channel.class);
channelField = Accessors.getFieldAccessor(field);
}
Channel channel = (Channel) channelField.get(networkManager);
manager.injectChannel(channel);
ProtocolLogger.log("Successfully injected channel " + channel);
} catch (Throwable ex) {
ProtocolLogger.log(Level.SEVERE, "Failed to inject channel", ex);
}
}
}
@@ -15,26 +15,6 @@
*/
package com.comphenix.protocol;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.InternalManager;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.scheduler.DefaultScheduler;
import com.comphenix.protocol.scheduler.FoliaScheduler;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.comphenix.protocol.scheduler.Task;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.*;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@@ -47,9 +27,29 @@ import java.util.logging.Level;
import java.util.logging.LogRecord;
import java.util.logging.Logger;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.DelegatedErrorReporter;
import com.comphenix.protocol.error.DetailedErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.error.Report;
import com.comphenix.protocol.error.ReportType;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.metrics.Statistics;
import com.comphenix.protocol.scheduler.DefaultScheduler;
import com.comphenix.protocol.scheduler.FoliaScheduler;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import com.comphenix.protocol.scheduler.Task;
import com.comphenix.protocol.updater.Updater;
import com.comphenix.protocol.updater.Updater.UpdateType;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.ChatExtensions;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Util;
import com.google.common.base.Splitter;
import com.google.common.collect.Iterables;
import org.bukkit.Server;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
@@ -58,7 +58,7 @@ import org.bukkit.plugin.java.JavaPlugin;
*
* @author Kristian
*/
public class ProtocolLib extends JavaPlugin {
public abstract class ProtocolLib extends JavaPlugin {
// Every possible error or warning report type
public static final ReportType REPORT_CANNOT_DELETE_CONFIG = new ReportType(
"Cannot delete old ProtocolLib configuration.");
@@ -91,8 +91,8 @@ public class ProtocolLib extends JavaPlugin {
// these fields are only existing once, we can make them static
private static Logger logger;
private static ProtocolConfig config;
private static InternalManager protocolManager;
private static ErrorReporter reporter = new BasicErrorReporter();
protected static PacketFilterManager protocolManager;
protected static ErrorReporter reporter = new BasicErrorReporter();
private Statistics statistics;
@@ -210,7 +210,7 @@ public class ProtocolLib extends JavaPlugin {
this.commandPacket = new CommandPacket(reporter, this, logger, this.commandFilter, protocolManager);
break;
case LOGGING:
this.packetLogging = new PacketLogging(this, protocolManager);
this.packetLogging = new PacketLogging(reporter, this, protocolManager);
break;
}
} catch (OutOfMemoryError e) {
@@ -336,7 +336,11 @@ public class ProtocolLib extends JavaPlugin {
this.registerCommand(PacketLogging.NAME, this.packetLogging);
// Player login and logout events
protocolManager.registerEvents(manager, this);
protocolManager.inject();
manager.registerEvents(new BukkitListener(protocolManager), this);
registerLoginListener();
// Worker that ensures that async packets are eventually sent
// It also performs the update check.
@@ -364,6 +368,8 @@ public class ProtocolLib extends JavaPlugin {
}
}
abstract void registerLoginListener();
// Used to check Minecraft version
private MinecraftVersion verifyMinecraftVersion() {
MinecraftVersion minimum = new MinecraftVersion(ProtocolLibrary.MINIMUM_MINECRAFT_VERSION);
@@ -437,26 +443,7 @@ public class ProtocolLib extends JavaPlugin {
}
}
private void registerCommand(String name, CommandExecutor executor) {
try {
// Ignore these - they must have printed an error already
if (executor == null) {
return;
}
PluginCommand command = this.getCommand(name);
// Try to load the command
if (command != null) {
command.setExecutor(executor);
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
abstract void registerCommand(String name, CommandBase command);
/**
* Disable the current plugin.
@@ -0,0 +1,19 @@
package com.comphenix.protocol;
public final class ProtocolLibPaper extends ProtocolLib {
@Override
void registerLoginListener() {
try {
Class.forName("io.papermc.paper.event.connection.PlayerConnectionValidateLoginEvent");
getServer().getPluginManager().registerEvents(new com.comphenix.protocol.PaperLoginListener(protocolManager), this);
} catch (ClassNotFoundException ignored) {
getServer().getPluginManager().registerEvents(new LegacyLoginListener(protocolManager), this);
}
}
@Override
public void registerCommand(String name, CommandBase command) {
registerCommand(name, (stack, args) -> command.onCommand(stack.getSender(), args));
}
}
@@ -0,0 +1,30 @@
package com.comphenix.protocol;
import com.comphenix.protocol.error.Report;
import org.bukkit.command.PluginCommand;
public final class ProtocolLibSpigot extends ProtocolLib {
@Override
void registerLoginListener() {
getServer().getPluginManager().registerEvents(new LegacyLoginListener(protocolManager), this);
}
@Override
void registerCommand(String name, CommandBase command) {
try {
PluginCommand pluginCmd = this.getCommand(name);
// Try to load the command
if (pluginCmd != null) {
pluginCmd.setExecutor((sender, _c, _l, args) ->
command.onCommand(sender, args));
} else {
throw new RuntimeException("plugin.yml might be corrupt.");
}
} catch (RuntimeException e) {
reporter.reportWarning(this,
Report.newBuilder(REPORT_CANNOT_REGISTER_COMMAND).messageParam(name, e.getMessage()).error(e));
}
}
}
@@ -18,7 +18,7 @@ import com.comphenix.protocol.error.BasicErrorReporter;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.scheduler.ProtocolScheduler;
import org.apache.commons.lang.Validate;
import com.comphenix.protocol.utility.Validate;
import org.bukkit.plugin.Plugin;
/**
@@ -48,8 +48,8 @@ public class ProtocolLibrary {
private static ProtocolScheduler scheduler;
private static ErrorReporter reporter = new BasicErrorReporter();
private static boolean updatesDisabled;
private static boolean initialized;
private static boolean updatesDisabled = false;
private static boolean initialized = false;
protected static void init(Plugin plugin, ProtocolConfig config, ProtocolManager manager,
ProtocolScheduler scheduler, ErrorReporter reporter) {
@@ -30,20 +30,17 @@ 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.ProtocolLibrary;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.collections.ExpireHashMap;
import com.comphenix.protocol.error.Report.ReportBuilder;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.reflect.PrettyPrinter;
import com.google.common.base.Preconditions;
import com.google.common.primitives.Primitives;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
/**
* Internal class used to handle exceptions.
@@ -472,7 +469,7 @@ public class DetailedErrorReporter implements ErrorReporter {
} if (isSimpleType(value) || value instanceof Class<?>) {
return value.toString();
} else {
try {
/*try {
if (!apacheCommonsMissing)
return ToStringBuilder.reflectionToString(value, ToStringStyle.MULTI_LINE_STYLE, false, null);
} catch (LinkageError ex) {
@@ -483,7 +480,7 @@ public class DetailedErrorReporter implements ErrorReporter {
} catch (Throwable ex) {
// Don't use the error logger to log errors in error logging (that could lead to infinite loops)
ProtocolLogger.log(Level.WARNING, "Cannot convert to a String with Apache: " + ex.getMessage());
}
}*/
// Use our custom object printer instead
try {
@@ -10,22 +10,8 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import javax.annotation.Nonnull;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
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;
@@ -68,7 +54,20 @@ 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 com.comphenix.protocol.utility.Validate;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.World;
import org.bukkit.WorldType;
import org.bukkit.entity.Entity;
import org.bukkit.entity.EntityType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.MerchantRecipe;
import org.bukkit.potion.PotionEffectType;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
public abstract class AbstractStructure {
protected transient Object handle;
@@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import org.apache.commons.lang.Validate;
import com.comphenix.protocol.utility.Validate;
/**
* Stores and retrieves metadata for applicable packet objects.
@@ -36,6 +36,7 @@ import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.ByteBuddyFactory;
import com.comphenix.protocol.utility.Util;
import io.papermc.paper.persistence.PersistentDataContainerView;
import net.bytebuddy.description.ByteCodeElement;
import net.bytebuddy.description.modifier.Visibility;
import net.bytebuddy.dynamic.loading.ClassLoadingStrategy;
@@ -60,7 +61,6 @@ import org.bukkit.World;
import org.bukkit.entity.EntityType;
import org.bukkit.entity.Player;
import org.bukkit.profile.PlayerProfile;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -157,11 +157,28 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return null;
}
/**
* @param b
* @return
*/
@Override
public @org.jspecify.annotations.Nullable Location getRespawnLocation(boolean b) {
return null;
}
@Override
public Location getLocation() {
return null;
}
/**
* @return
*/
@Override
public PersistentDataContainerView getPersistentDataContainer() {
return null;
}
// TODO do we need to implement this?
public void incrementStatistic(Statistic statistic) throws IllegalArgumentException {
@@ -241,8 +258,11 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return uuid;
}
/**
* @return
*/
@Override
public @NotNull PlayerProfile getPlayerProfile() {
public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() {
return null;
}
@@ -288,6 +308,14 @@ class SerializedOfflinePlayer implements OfflinePlayer, Serializable {
return online;
}
/**
* @return
*/
@Override
public boolean isConnected() {
return false;
}
@Override
public boolean isWhitelisted() {
return whitelisted;
@@ -39,7 +39,7 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.Validate;
import com.comphenix.protocol.utility.Validate;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
@@ -1,8 +1,6 @@
package com.comphenix.protocol.injector;
import com.comphenix.protocol.ProtocolManager;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
/**
* Yields access to the internal hook configuration.
@@ -11,14 +9,6 @@ import org.bukkit.plugin.PluginManager;
*/
public interface InternalManager extends ProtocolManager {
/**
* Register this protocol manager on Bukkit.
*
* @param manager - Bukkit plugin manager that provides player join/leave events.
* @param plugin - the parent plugin.
*/
void registerEvents(PluginManager manager, final Plugin plugin);
/**
* Called when ProtocolLib is closing.
*/
@@ -1,10 +1,12 @@
package com.comphenix.protocol.injector;
import javax.annotation.Nonnull;
import com.comphenix.protocol.ProtocolLib;
import com.comphenix.protocol.async.AsyncFilterManager;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.utility.MinecraftVersion;
import javax.annotation.Nonnull;
import org.bukkit.Server;
import org.bukkit.plugin.Plugin;
@@ -112,7 +114,7 @@ public class PacketFilterBuilder {
*
* @return A new packet filter manager.
*/
public InternalManager build() {
public PacketFilterManager build() {
if (this.reporter == null) {
throw new IllegalArgumentException("reporter cannot be NULL.");
}
@@ -8,21 +8,6 @@ import java.util.Objects;
import java.util.Set;
import java.util.logging.Level;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
import com.comphenix.protocol.AsynchronousManager;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
@@ -47,7 +32,15 @@ import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.netty.manager.NetworkManagerInjector;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableSet;
import io.netty.channel.Channel;
import org.bukkit.Location;
import org.bukkit.Server;
import org.bukkit.World;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class PacketFilterManager implements ListenerManager, InternalManager {
@@ -327,24 +320,30 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
@Override
public void removePacketListener(PacketListener listener) {
if (!this.closed && this.registeredListeners.remove(listener)) {
ListeningWhitelist outbound = listener.getSendingWhitelist();
ListeningWhitelist inbound = listener.getReceivingWhitelist();
if (this.closed || !this.registeredListeners.remove(listener)) {
return;
}
// remove outbound listeners (if any)
if (outbound != null && outbound.isEnabled()) {
this.outboundListeners.removeListener(listener);
}
ListeningWhitelist outbound = listener.getSendingWhitelist();
ListeningWhitelist inbound = listener.getReceivingWhitelist();
// remove inbound listeners (if any)
if (inbound != null && inbound.isEnabled()) {
this.inboundListeners.removeListener(listener);
}
// remove outbound listeners (if any)
if (outbound != null && outbound.isEnabled()) {
this.outboundListeners.removeListener(listener);
}
// remove inbound listeners (if any)
if (inbound != null && inbound.isEnabled()) {
this.inboundListeners.removeListener(listener);
}
}
@Override
public void removePacketListeners(Plugin plugin) {
if (this.closed || this.plugin.equals(plugin)) {
return;
}
for (PacketListener listener : this.getPacketListeners()) {
if (Objects.equals(listener.getPlugin(), plugin)) {
this.removePacketListener(listener);
@@ -421,40 +420,26 @@ public class PacketFilterManager implements ListenerManager, InternalManager {
}
}
@Override
public void registerEvents(PluginManager manager, Plugin plugin) {
if (!this.closed && !this.injected) {
// prevent duplicate event registrations / injections
this.injected = true;
this.networkManagerInjector.inject();
public void removePlayer(Player player) {
asyncFilterManager.removePlayer(player);
}
// all listeners we need, this is a bit messy, but it makes the job correctly
manager.registerEvents(new Listener() {
public void injectPlayer(Player player) {
networkManagerInjector.getInjector(player).inject();
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleLogin(PlayerLoginEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
public void injectChannel(Channel channel) {
networkManagerInjector.getInjector(channel).inject();
}
@EventHandler(priority = EventPriority.LOWEST)
public void handleJoin(PlayerJoinEvent event) {
networkManagerInjector.getInjector(event.getPlayer()).inject();
}
@EventHandler(priority = EventPriority.MONITOR)
public void handleQuit(PlayerQuitEvent event) {
PacketFilterManager.this.asyncFilterManager.removePlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.MONITOR)
public void handlePluginUnload(PluginDisableEvent event) {
// don't do this for our plugin!
if (event.getPlugin() != PacketFilterManager.this.plugin) {
PacketFilterManager.this.removePacketListeners(event.getPlugin());
}
}
}, plugin);
public void inject() {
if (this.closed || this.injected) {
return;
}
// prevent duplicate event registrations / injections
this.injected = true;
this.networkManagerInjector.inject();
}
@Override
@@ -2,12 +2,12 @@ package com.comphenix.protocol.injector.netty;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import org.bukkit.entity.Player;
/**
* Represents an injected client connection.
*
@@ -74,6 +74,8 @@ public interface Injector {
*/
void setPlayer(Player player);
boolean hasValidPlayer();
boolean isConnected();
/**
@@ -2,13 +2,14 @@ package com.comphenix.protocol.injector.netty.channel;
import java.net.SocketAddress;
import org.bukkit.entity.Player;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.PacketType.Protocol;
import com.comphenix.protocol.events.NetworkMarker;
import com.comphenix.protocol.injector.netty.Injector;
import com.comphenix.protocol.injector.netty.WirePacket;
import com.comphenix.protocol.injector.temporary.TemporaryPlayer;
import org.bukkit.entity.Player;
final class EmptyInjector implements Injector {
@@ -72,6 +73,11 @@ final class EmptyInjector implements Injector {
this.player = player;
}
@Override
public boolean hasValidPlayer() {
return player != null && !(player instanceof TemporaryPlayer);
}
@Override
public boolean isConnected() {
return false;
@@ -16,12 +16,9 @@ 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.ProtocolLogger;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerManager;
import com.comphenix.protocol.injector.netty.Injector;
@@ -29,10 +26,12 @@ 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 com.google.common.collect.MapMaker;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Represents an injector factory.
@@ -85,12 +84,8 @@ public class InjectionFactory {
// try to get the injector using the player reference first
Injector injector = this.playerLookup.get(player);
if (injector == null) {
injector = this.getTemporaryInjector(player);
}
// check if we found an injector
if (injector != null && !injector.isClosed()) {
ProtocolLogger.log(player.getName() + " has a cached injector. Player valid: " + injector.hasValidPlayer());
return injector;
}
@@ -110,7 +105,10 @@ public class InjectionFactory {
this.playerLookup.remove(injector.getPlayer());
this.cacheInjector(player, injector);
// re-set the player of the injection
ProtocolLogger.log("Reusing existing injector and setting player to " + player.getName());
injector.setPlayer(player);
} else {
ProtocolLogger.log(player.getName() + "'s channel already has an injector. Player valid: " + injector.hasValidPlayer());
}
} else {
// construct a new injector as it seems like we have none yet
@@ -11,9 +11,6 @@ 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;
import com.comphenix.protocol.ProtocolLibrary;
@@ -29,6 +26,7 @@ import com.comphenix.protocol.injector.NetworkProcessor;
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.injector.temporary.TemporaryPlayer;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
@@ -43,6 +41,8 @@ import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoop;
import io.netty.util.AttributeKey;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
public class NettyChannelInjector implements Injector {
@@ -343,7 +343,7 @@ public class NettyChannelInjector implements Injector {
@Override
public Player getPlayer() {
// if the player was already resolved there is no need to do further lookups
if (this.player != null) {
if (this.player != null && !(this.player instanceof TemporaryPlayer)) {
return this.player;
}
@@ -370,6 +370,11 @@ public class NettyChannelInjector implements Injector {
this.injectionFactory.cacheInjector(player.getName(), this);
}
@Override
public boolean hasValidPlayer() {
return this.player != null && !(player instanceof TemporaryPlayer);
}
@Override
public boolean isConnected() {
return this.channel.isActive();
@@ -7,9 +7,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import com.comphenix.protocol.ProtocolLogger;
import com.comphenix.protocol.error.ErrorReporter;
import com.comphenix.protocol.injector.ListenerManager;
@@ -23,7 +20,10 @@ import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.Pair;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
public class NetworkManagerInjector {
@@ -57,6 +57,10 @@ public class NetworkManagerInjector {
return this.injectionFactory.fromPlayer(player);
}
public Injector getInjector(Channel channel) {
return injectionFactory.fromChannel(channel);
}
@SuppressWarnings("unchecked")
public void inject() {
if (this.closed || this.injected) {
@@ -1899,4 +1899,16 @@ public final class MinecraftReflection {
return isMojangMapped;
}
public static Optional<Class<?>> getDataComponentMapClass() {
return getOptionalNMS("core.component.DataComponentMap");
}
public static Optional<Class<?>> getDataComponentHolderClass() {
return getOptionalNMS("core.component.DataComponentHolder");
}
public static Optional<Class<?>> getTypedDataComponentClass() {
return getOptionalNMS("core.component.TypedDataComponent");
}
}
@@ -0,0 +1,17 @@
package com.comphenix.protocol.utility;
public final class Validate {
private Validate() {}
public static void notNull(Object obj, String err) {
if (obj == null) {
throw new IllegalArgumentException(err);
}
}
public static void isTrue(boolean bool, String err) {
if (!bool) {
throw new IllegalArgumentException(err);
}
}
}
@@ -0,0 +1,12 @@
package com.comphenix.protocol.utility;
public final class WordUtils {
private WordUtils() {}
public static String capitalize(String str) {
if (str == null || str.isEmpty()) {
return str;
}
return str.substring(0, 1).toUpperCase() + str.substring(1).toLowerCase();
}
}
@@ -11,7 +11,7 @@ import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import org.apache.commons.lang.Validate;
import com.comphenix.protocol.utility.Validate;
import org.bukkit.ChatColor;
import org.bukkit.GameMode;
@@ -37,7 +37,7 @@ import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedDataWatcher.WrappedDataWatcherObject;
import com.comphenix.protocol.wrappers.collection.ConvertedMap;
import org.apache.commons.lang.Validate;
import com.comphenix.protocol.utility.Validate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Entity;
@@ -53,7 +53,14 @@ public class MinecraftKey {
* @param key The key
*/
public MinecraftKey(String key) {
this("minecraft", key);
if (key.contains(":")) {
String[] split = key.split(":", 2);
this.prefix = split[0];
this.key = split[1];
} else {
this.prefix = "minecraft";
this.key = key;
}
}
/**
@@ -0,0 +1,45 @@
package com.comphenix.protocol.wrappers;
import com.comphenix.protocol.utility.MinecraftReflection;
public class ResourceKey {
private static final AutoWrapper<ResourceKey> AUTO_WRAPPER = AutoWrapper.wrap(ResourceKey.class, MinecraftReflection.getResourceKey())
.field(0, MinecraftKey.getConverter())
.field(1, MinecraftKey.getConverter());
MinecraftKey registryName;
MinecraftKey location;
ResourceKey() {
}
public ResourceKey(MinecraftKey registryName, MinecraftKey location) {
this.registryName = registryName;
this.location = location;
}
public MinecraftKey getRegistryName() {
return registryName;
}
public MinecraftKey getLocation() {
return location;
}
public void setRegistryName(MinecraftKey registryName) {
this.registryName = registryName;
}
public void setLocation(MinecraftKey location) {
this.location = location;
}
public static ResourceKey fromGeneric(Object generic) {
return AUTO_WRAPPER.wrap(generic);
}
public Object toGeneric() {
return AUTO_WRAPPER.unwrap(this);
}
}
@@ -0,0 +1,181 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.RecordComponent;
import java.util.Optional;
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.reflect.cloning.AggregateCloner;
import com.comphenix.protocol.utility.MinecraftReflection;
/**
* Represents a single data component in Minecraft, which is a key-value pair
* used to store metadata about game objects such as items, mobs, and block entities.
* <p>
* This class provides methods to create, access, and manipulate data components,
* as well as to convert them to and from their underlying NMS handles.
*
* @see WrappedDataComponentHolder
*/
public final class WrappedDataComponent {
private static final Optional<Class<?>> HANDLE_TYPE = MinecraftReflection.getTypedDataComponentClass();
private static MethodAccessor TYPE_ACCESSOR;
private static MethodAccessor VALUE_ACCESSOR;
private static ConstructorAccessor CTOR_ACCESSOR;
private final Object keyHandle;
private final String key;
private Object value;
static {
initialize();
}
private static void initialize() {
if (HANDLE_TYPE.isEmpty()) {
return;
}
Class<?> clazz = HANDLE_TYPE.get();
RecordComponent typeComponent = clazz.getRecordComponents()[0];
TYPE_ACCESSOR = Accessors.getMethodAccessor(typeComponent.getAccessor());
RecordComponent valueComponent = clazz.getRecordComponents()[1];
VALUE_ACCESSOR = Accessors.getMethodAccessor(valueComponent.getAccessor());
CTOR_ACCESSOR = Accessors.getConstructorAccessor(clazz, typeComponent.getType(), valueComponent.getType());
}
WrappedDataComponent(Object keyHandle, String key, Object value) {
this.keyHandle = keyHandle;
this.key = key;
this.value = value;
}
/**
* @return the key of this data component, e.g. "minecraft:custom_data".
*/
public String getKey() {
return key;
}
/**
* Updates the in-memory value of this data component.
* <br>
* <b>NOTE:</b> this does <b>NOT</b> update the underlying handle
* @param value new NMS value
*/
public void setValue(Object value) {
this.value = value;
}
/**
* @return the current in-memory value of this data component
* @param <T> desired return type
*/
@SuppressWarnings("unchecked")
public <T> T getValue() {
return (T) value;
}
/**
* Creates a new WrappedDataComponent from an existing NMS handle.
* @param handle NMS handle
* @return new WrappedDataComponent instance
*/
public static WrappedDataComponent fromHandle(Object handle) {
if (TYPE_ACCESSOR == null || VALUE_ACCESSOR == null) {
throw new IllegalStateException("Unable to access TypedDataComponent");
}
Object typeHandle = TYPE_ACCESSOR.invoke(handle);
Object value = VALUE_ACCESSOR.invoke(handle);
WrappedRegistry componentTypeRegistry = WrappedRegistry.getDataComponentTypeRegistry();
String key = componentTypeRegistry.getKey(typeHandle).getFullKey();
return new WrappedDataComponent(typeHandle, key, value);
}
/**
* Creates a new NMS handle for this data component using the current value.
* @return new NMS handle
*/
public Object getHandle() {
if (CTOR_ACCESSOR == null) {
throw new IllegalStateException("Unable to access TypedDataComponent constructor");
}
return CTOR_ACCESSOR.invoke(keyHandle, value);
}
/**
* Creates a new WrappedDataComponent with the specified key and no value.
* @param key the key of the data component, e.g. "minecraft:custom_data".
* @return new WrappedDataComponent instance
*/
public static WrappedDataComponent create(String key) {
return create(key, null);
}
/**
* Creates a new WrappedDataComponent with the specified key and value.
* @param key the key of the data component, e.g. "minecraft:custom_data".
* @param value the NMS value of the data component, e.g. an ItemStack or Integer.
* @return new WrappedDataComponent instance
*/
public static WrappedDataComponent create(String key, Object value) {
WrappedRegistry componentTypeRegistry = WrappedRegistry.getDataComponentTypeRegistry();
Object keyHandle = componentTypeRegistry.get(key);
if (keyHandle == null) {
throw new IllegalArgumentException("Invalid key: " + key);
}
return new WrappedDataComponent(keyHandle, key, value);
}
/**
* Creates a shallow clone of this WrappedDataComponent, which means it copies the key and value references,
* but does not clone the value itself. This should cover most cases where the value is immutable
* (e.g. strings, numbers, enums).
* @return a new WrappedDataComponent instance with the same key and value references
*/
public WrappedDataComponent shallowClone() {
return new WrappedDataComponent(keyHandle, key, value);
}
/**
* Creates a deep clone of this WrappedDataComponent, which means it clones the value as well.
* This is useful when the value is mutable (e.g. books, NBT data) and you want to ensure that
* changes to the cloned component do not affect the original component.
* @return a new WrappedDataComponent instance with a cloned value
*/
public WrappedDataComponent deepClone() {
Object clonedValue = AggregateCloner.DEFAULT.clone(value);
return new WrappedDataComponent(keyHandle, key, clonedValue);
}
@Override
public String toString() {
return "WrappedDataComponent[" + key + "=>" + value + "]";
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (!(obj instanceof WrappedDataComponent other)) return false;
return key.equals(other.key) && value.equals(other.value);
}
@Override
public int hashCode() {
int result = key.hashCode();
result = 31 * result + (value != null ? value.hashCode() : 0);
return result;
}
}
@@ -0,0 +1,77 @@
package com.comphenix.protocol.wrappers;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
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 org.bukkit.inventory.ItemStack;
/**
* Provides access to a DataComponentHolder, which is an object that can hold
* various (meta)data components
*/
public final class WrappedDataComponentHolder extends AbstractWrapper {
private static MethodAccessor GET_COMPONENTS;
private static MethodAccessor GET_COMPONENT;
private static Optional<Class<?>> DATA_COMPONENT_MAP_CLASS = MinecraftReflection.getDataComponentMapClass();
private static Optional<Class<?>> DATA_COMPONENT_HOLDER_CLASS = MinecraftReflection.getDataComponentHolderClass();
static {
initialize();
}
private static void initialize() {
if (DATA_COMPONENT_MAP_CLASS.isEmpty() || DATA_COMPONENT_HOLDER_CLASS.isEmpty()) {
return;
}
Class<?> mapClass = DATA_COMPONENT_MAP_CLASS.get();
Class<?> holderClass = DATA_COMPONENT_HOLDER_CLASS.get();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(holderClass, true);
GET_COMPONENTS = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(0)
.returnTypeExact(mapClass)
.build()));
GET_COMPONENT = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract.newBuilder()
.parameterCount(1)
//.requireModifier(Modifier.PUBLIC)
.banModifier(Modifier.STATIC)
.parameterExactType(MinecraftReflection.getMinecraftClass("core.component.DataComponentType"))
.build()));
}
public WrappedDataComponentHolder(Object handle) {
super(DATA_COMPONENT_HOLDER_CLASS.orElseThrow(() -> new IllegalStateException("DataComponentHolder class is not available in this version")));
setHandle(handle);
}
public Map<String, WrappedDataComponent> getAllComponents() {
Iterable<?> components = (Iterable<?>) GET_COMPONENTS.invoke(handle);
Map<String, WrappedDataComponent> wrapped = new HashMap<>();
for (Object componentHandle : components) {
WrappedDataComponent component = WrappedDataComponent.fromHandle(componentHandle);
wrapped.put(component.getKey(), component);
}
return wrapped;
}
public WrappedDataComponent getComponent(String key) {
Object keyHandle = WrappedRegistry.getDataComponentTypeRegistry().get(key);
Object valueHandle = GET_COMPONENT.invoke(handle, keyHandle);
return new WrappedDataComponent(keyHandle, key, valueHandle);
}
public static WrappedDataComponentHolder fromItemStack(ItemStack itemStack) {
Object nmsStack = MinecraftReflection.getMinecraftItemStack(itemStack);
return new WrappedDataComponentHolder(nmsStack);
}
}
@@ -36,12 +36,12 @@ import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.utility.Validate;
import com.comphenix.protocol.wrappers.EnumWrappers.Direction;
import com.comphenix.protocol.wrappers.EnumWrappers.EntityPose;
import com.comphenix.protocol.wrappers.nbt.NbtCompound;
import com.google.common.collect.ImmutableBiMap;
import org.apache.commons.lang.Validate;
import org.bukkit.entity.Entity;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
@@ -1,14 +1,5 @@
package com.comphenix.protocol.wrappers;
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.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableMap;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
@@ -19,21 +10,79 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
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.FuzzyMatchers;
import com.comphenix.protocol.reflect.fuzzy.FuzzyMethodContract;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.google.common.collect.ImmutableMap;
public class WrappedRegistry {
// map of NMS class to registry instance
private static final Map<Class<?>, WrappedRegistry> REGISTRY;
private static final Map<Class<?>, WrappedRegistry> BY_CLASS;
private static final Map<String, WrappedRegistry> BY_KEY;
private static final MethodAccessor GET;
private static final MethodAccessor GET_ID;
private static final MethodAccessor GET_KEY;
private static final MethodAccessor GET_BY_KEY;
private static final MethodAccessor GET_ITEM_ID;
private static final MethodAccessor GET_ITEM_KEY;
private static final MethodAccessor GET_REGISTRY_KEY;
private static final MethodAccessor GET_HOLDER;
static {
Map<Class<?>, WrappedRegistry> regMap = new HashMap<>();
Class<?> iRegistry = MinecraftReflection.getIRegistry();
FuzzyReflection fuzzy = FuzzyReflection.fromClass(iRegistry, false);
GET_BY_KEY = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeMatches(FuzzyMatchers.and(FuzzyMatchers.assignable(Object.class), FuzzyMatchers.except(Optional.class)))
.requireModifier(Modifier.ABSTRACT)
.parameterExactType(MinecraftReflection.getMinecraftKeyClass())
.build()));
GET_ITEM_ID = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeExact(int.class)
.requireModifier(Modifier.ABSTRACT)
.parameterDerivedOf(Object.class)
.build()));
GET_ITEM_KEY = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeExact(MinecraftReflection.getMinecraftKeyClass())
.build()));
GET_REGISTRY_KEY = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(0)
.returnTypeExact(MinecraftReflection.getResourceKey())
.build()));
MethodAccessor getHolder = null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
try {
getHolder = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.banModifier(Modifier.STATIC)
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.PUBLIC)
.build()));
} catch (IllegalArgumentException ignored) {
}
}
GET_HOLDER = getHolder;
Map<Class<?>, WrappedRegistry> byClass = new HashMap<>();
Map<String, WrappedRegistry> byKey = new HashMap<>();
// get the possible registry fields
Class<?> iRegistry = MinecraftReflection.getIRegistry();
Class<?> builtInRegistries = MinecraftReflection.getBuiltInRegistries();
Set<Field> registries = FuzzyReflection.combineArrays(
iRegistry == null ? null : iRegistry.getFields(),
@@ -43,95 +92,75 @@ public class WrappedRegistry {
for (Field field : registries) {
try {
// make sure it's actually a registry
if (iRegistry.isAssignableFrom(field.getType())) {
Type genType = field.getGenericType();
if (genType instanceof ParameterizedType) {
ParameterizedType par = (ParameterizedType) genType;
Type paramType = par.getActualTypeArguments()[0];
if (paramType instanceof Class) {
// for example Registry<Item>
regMap.put((Class<?>) paramType, new WrappedRegistry(field.get(null)));
} else if (paramType instanceof ParameterizedType) {
// for example Registry<EntityType<?>>
par = (ParameterizedType) paramType;
paramType = par.getActualTypeArguments()[0];
if (paramType instanceof WildcardType) {
// some registry types are even more nested, but we don't want them
// for example Registry<Codec<ChunkGenerator>>, Registry<Codec<WorldChunkManager>>
WildcardType wildcard = (WildcardType) paramType;
if (wildcard.getUpperBounds().length != 1 || wildcard.getLowerBounds().length > 0) {
continue;
}
if (!iRegistry.isAssignableFrom(field.getType())) {
continue;
}
// we only want types with an undefined upper bound (aka. ?)
if (wildcard.getUpperBounds()[0] != Object.class) {
continue;
}
}
Type genType = field.getGenericType();
if (!(genType instanceof ParameterizedType par)) {
continue;
}
paramType = par.getRawType();
if (paramType instanceof Class<?>) {
// there might be duplicate registries, like the codec registries
// we don't want them to be registered here
regMap.put((Class<?>) paramType, new WrappedRegistry(field.get(null)));
}
Class<?> registryClass = null;
Type paramType = par.getActualTypeArguments()[0];
if (paramType instanceof Class<?> paramClass) {
// for example Registry<Item>
registryClass = paramClass;
} else if (paramType instanceof ParameterizedType) {
// for example Registry<EntityType<?>>
par = (ParameterizedType) paramType;
paramType = par.getActualTypeArguments()[0];
if (paramType instanceof WildcardType wildcard) {
// some registry types are even more nested, but we don't want them
// for example Registry<Codec<ChunkGenerator>>, Registry<Codec<WorldChunkManager>>
if (wildcard.getUpperBounds().length != 1 || wildcard.getLowerBounds().length > 0) {
continue;
}
// we only want types with an undefined upper bound (aka. ?)
if (wildcard.getUpperBounds()[0] != Object.class) {
continue;
}
}
paramType = par.getRawType();
if (paramType instanceof Class<?> paramClass) {
// there might be duplicate registries, like the codec registries
// we don't want them to be registered here
registryClass = paramClass;
}
}
Object handle = field.get(null);
ResourceKey key = ResourceKey.fromGeneric(GET_REGISTRY_KEY.invoke(handle));
WrappedRegistry wrapped = new WrappedRegistry(handle, key);
if (registryClass != null) {
byClass.put(registryClass, wrapped);
}
String registryName = key.getLocation().getFullKey();
byKey.put(registryName, wrapped);
} catch (ReflectiveOperationException ignored) {
}
}
}
REGISTRY = ImmutableMap.copyOf(regMap);
FuzzyReflection fuzzy = FuzzyReflection.fromClass(iRegistry, false);
GET = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeMatches(FuzzyMatchers.and(FuzzyMatchers.assignable(Object.class), FuzzyMatchers.except(Optional.class)))
.requireModifier(Modifier.ABSTRACT)
.parameterExactType(MinecraftReflection.getMinecraftKeyClass())
.build()));
GET_ID = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeExact(int.class)
.requireModifier(Modifier.ABSTRACT)
.parameterDerivedOf(Object.class)
.build()));
GET_KEY = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.returnTypeExact(MinecraftReflection.getMinecraftKeyClass())
.build()));
MethodAccessor getHolder = null;
if (MinecraftVersion.FEATURE_PREVIEW_UPDATE.atOrAbove()) {
try {
getHolder = Accessors.getMethodAccessor(fuzzy.getMethod(FuzzyMethodContract
.newBuilder()
.parameterCount(1)
.banModifier(Modifier.STATIC)
.returnTypeExact(MinecraftReflection.getHolderClass())
.requireModifier(Modifier.PUBLIC)
.build()));
} catch (IllegalArgumentException ignored) {
}
}
GET_HOLDER = getHolder;
BY_CLASS = ImmutableMap.copyOf(byClass);
BY_KEY = ImmutableMap.copyOf(byKey);
}
private final Object handle;
private final ResourceKey key;
private WrappedRegistry(Object handle) {
private WrappedRegistry(Object handle, ResourceKey key) {
this.handle = handle;
this.key = key;
}
public Object get(MinecraftKey key) {
return GET.invoke(handle, MinecraftKey.getConverter().getGeneric(key));
return GET_BY_KEY.invoke(handle, MinecraftKey.getConverter().getGeneric(key));
}
public Object get(String key) {
@@ -139,7 +168,11 @@ public class WrappedRegistry {
}
public MinecraftKey getKey(Object generic) {
return MinecraftKey.getConverter().getSpecific(GET_KEY.invoke(handle, generic));
return MinecraftKey.getConverter().getSpecific(GET_ITEM_KEY.invoke(handle, generic));
}
public ResourceKey getKey() {
return key;
}
public int getId(MinecraftKey key) {
@@ -151,7 +184,7 @@ public class WrappedRegistry {
}
public int getId(Object entry) {
return (int) GET_ID.invoke(this.handle, entry);
return (int) GET_ITEM_ID.invoke(this.handle, entry);
}
public Object getHolder(Object generic) {
@@ -170,7 +203,15 @@ public class WrappedRegistry {
return getRegistry(MinecraftReflection.getSoundEffectClass());
}
public static WrappedRegistry getDataComponentTypeRegistry() {
return getByKey("minecraft:data_component_type");
}
public static WrappedRegistry getRegistry(Class<?> type) {
return REGISTRY.get(type);
return BY_CLASS.get(type);
}
public static WrappedRegistry getByKey(String key) {
return BY_KEY.get(key);
}
}
+6
View File
@@ -0,0 +1,6 @@
name: ProtocolLib
version: ${version}
main: com.comphenix.protocol.ProtocolLibPaper
api-version: '1.21.8'
website: https://github.com/dmulloy2/ProtocolLib
load: STARTUP
+1 -1
View File
@@ -3,7 +3,7 @@ version: ${version}
description: Provides read/write access to the Minecraft protocol.
authors: [dmulloy2, comphenix]
main: com.comphenix.protocol.ProtocolLib
main: com.comphenix.protocol.ProtocolLibSpigot
load: STARTUP
database: false
api-version: "1.13"
@@ -31,9 +31,8 @@ public class WrappedComponentStyleTest {
@Test
public void testStyleAdventureConversion() {
Style adventureStyle = Style.style(NamedTextColor.GREEN, TextDecoration.BOLD);
// TODO(fix): wait for https://github.com/KyoriPowered/adventure/issues/1194 to be fixed
// .clickEvent(ClickEvent.changePage(10));
Style adventureStyle = Style.style(NamedTextColor.GREEN, TextDecoration.BOLD)
.clickEvent(ClickEvent.changePage(10));
WrappedComponentStyle wrapped = AdventureComponentConverter.fromStyle(adventureStyle);
assertEquals(adventureStyle, AdventureComponentConverter.fromWrapper(wrapped));
@@ -0,0 +1,26 @@
package com.comphenix.protocol.wrappers;
import java.util.Map;
import com.comphenix.protocol.BukkitInitialization;
import org.bukkit.Material;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertFalse;
public class WrappedDataComponentHolderTest {
@BeforeAll
public static void beforeAll() {
BukkitInitialization.initializeAll();
}
@Test
public void testFromItemStack() {
WrappedDataComponentHolder holder = WrappedDataComponentHolder.fromItemStack(new org.bukkit.inventory.ItemStack(Material.DIAMOND_SWORD));
Map<String, WrappedDataComponent> components = holder.getAllComponents();
assertFalse(components.isEmpty(), "Components should not be empty");
}
}
@@ -0,0 +1,52 @@
package com.comphenix.protocol.wrappers;
import java.util.Map;
import com.comphenix.protocol.BukkitInitialization;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
public class WrappedDataComponentTest {
@BeforeAll
public static void beforeAll() {
BukkitInitialization.initializeAll();
}
@Test
public void testShallowClone() {
WrappedDataComponentHolder holder = new WrappedDataComponentHolder(new ItemStack(Items.DIAMOND_AXE));
Map<String, WrappedDataComponent> components = holder.getAllComponents();
assertFalse(components.isEmpty(), "Components should not be empty");
for (WrappedDataComponent component : components.values()) {
WrappedDataComponent clone = component.shallowClone();
assertNotSame(component, clone, "Clone should not be the same instance");
assertEquals(component, clone, "Clone should be equal to the original component");
assertTrue(component.getValue().equals(clone.getValue()), "Clone value should match original");
}
}
@Test
public void testDeepCloneWithPrimitive() {
WrappedDataComponent component = WrappedDataComponent.create("minecraft:damage", 10);
WrappedDataComponent clone = component.deepClone();
assertNotSame(component, clone, "Clone should not be the same instance");
assertEquals(component, clone, "Clone should be equal to the original component");
assertEquals(10, (int)clone.getValue(), "Clone value should match original");
}
@Test
public void testDeepCloneWithEnum() {
WrappedDataComponent component = WrappedDataComponent.create("minecraft:item_model", Items.DIAMOND_SHOVEL);
WrappedDataComponent clone = component.deepClone();
assertNotSame(component, clone, "Clone should not be the same instance");
assertEquals(component, clone, "Clone should be equal to the original component");
assertEquals(Items.DIAMOND_SHOVEL, clone.getValue(), "Clone value should match original");
}
}