From 4c2135ac32219185d5cdb0884db2482c721eabe9 Mon Sep 17 00:00:00 2001 From: aro Date: Sat, 21 Jan 2023 12:11:11 +0100 Subject: [PATCH] feat: full 1.19.3 support ok this is SO weird. protocollib appears to have absolutly NO trouble modifing (via reflection) a IMMUTABLE list?? the code is so messy right now but i'll clean it up later, let's say it works for now. this commit also breaks all the other versions due to the way i get the internals soooooo..... --- nicko-core/dependency-reduced-pom.xml | 10 ++ nicko-core/pom.xml | 10 ++ .../net/artelnatif/nicko/NickoBukkit.java | 10 +- .../nicko/impl/InternalsProtocolLib.java | 10 ++ .../nicko/impl/InternalsProvider.java | 7 +- .../net/artelnatif/nicko/impl/v1_19_R2.java | 30 +++- .../net/artelnatif/nicko/impl/v1_19_R2_P.java | 147 ++++++++++++++++++ 7 files changed, 219 insertions(+), 5 deletions(-) create mode 100644 nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProtocolLib.java create mode 100644 v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2_P.java diff --git a/nicko-core/dependency-reduced-pom.xml b/nicko-core/dependency-reduced-pom.xml index dbcbfbf..9070ca3 100644 --- a/nicko-core/dependency-reduced-pom.xml +++ b/nicko-core/dependency-reduced-pom.xml @@ -78,6 +78,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + codemc-snapshots https://repo.codemc.io/repository/maven-snapshots/ @@ -160,6 +164,12 @@ 3.1.0 compile + + com.comphenix.protocol + ProtocolLib + 5.0.0-SNAPSHOT + compile + 17 diff --git a/nicko-core/pom.xml b/nicko-core/pom.xml index 0917b60..52570d0 100644 --- a/nicko-core/pom.xml +++ b/nicko-core/pom.xml @@ -36,6 +36,10 @@ spigot-repo https://hub.spigotmc.org/nexus/content/groups/public/ + + dmulloy2-repo + https://repo.dmulloy2.net/repository/public/ + codemc-snapshots https://repo.codemc.io/repository/maven-snapshots/ @@ -111,6 +115,12 @@ yamlconfig 1.1.1 + + + com.comphenix.protocol + ProtocolLib + 5.0.0-SNAPSHOT + diff --git a/nicko-core/src/main/java/net/artelnatif/nicko/NickoBukkit.java b/nicko-core/src/main/java/net/artelnatif/nicko/NickoBukkit.java index b028a4a..6d1b7ae 100644 --- a/nicko-core/src/main/java/net/artelnatif/nicko/NickoBukkit.java +++ b/nicko-core/src/main/java/net/artelnatif/nicko/NickoBukkit.java @@ -1,8 +1,11 @@ package net.artelnatif.nicko; +import com.comphenix.protocol.ProtocolLibrary; +import com.comphenix.protocol.ProtocolManager; import de.studiocode.invui.gui.structure.Structure; import de.studiocode.invui.item.builder.ItemBuilder; import de.studiocode.invui.item.impl.SimpleItem; +import net.artelnatif.nicko.bungee.BungeeCordSupport; import net.artelnatif.nicko.bungee.NickoBungee; import net.artelnatif.nicko.command.NickoCommand; import net.artelnatif.nicko.config.NickoConfiguration; @@ -17,7 +20,6 @@ import net.artelnatif.nicko.mojang.MojangAPI; import net.artelnatif.nicko.placeholder.PlaceHolderHook; import net.artelnatif.nicko.pluginchannel.PluginMessageHandler; import net.artelnatif.nicko.storage.PlayerDataStore; -import net.artelnatif.nicko.bungee.BungeeCordSupport; import org.bukkit.Material; import org.bukkit.command.PluginCommand; import org.bukkit.plugin.PluginDescriptionFile; @@ -36,6 +38,7 @@ public class NickoBukkit extends JavaPlugin { private MojangAPI mojangAPI; private PlayerDataStore dataStore; private LocaleFileManager localeFileManager; + private ProtocolManager protocolManager; public NickoBukkit() { this.unitTesting = false; } @@ -94,6 +97,7 @@ public class NickoBukkit extends JavaPlugin { saveDefaultConfig(); config = new NickoConfiguration(this); dataStore = new PlayerDataStore(this); + protocolManager = ProtocolLibrary.getProtocolManager(); getLogger().info("Loading internals..."); if (getInternals() == null) { @@ -163,6 +167,10 @@ public class NickoBukkit extends JavaPlugin { return localeFileManager; } + public ProtocolManager getProtocolManager() { + return protocolManager; + } + public boolean isUnitTesting() { return unitTesting; } diff --git a/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProtocolLib.java b/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProtocolLib.java new file mode 100644 index 0000000..9697648 --- /dev/null +++ b/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProtocolLib.java @@ -0,0 +1,10 @@ +package net.artelnatif.nicko.impl; + +import com.comphenix.protocol.ProtocolManager; +import net.artelnatif.nicko.NickoBukkit; + +public interface InternalsProtocolLib extends Internals { + default ProtocolManager getProtocolLib() { + return NickoBukkit.getInstance().getProtocolManager(); + } +} diff --git a/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProvider.java b/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProvider.java index f66428d..8b6a1b0 100644 --- a/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProvider.java +++ b/nicko-core/src/main/java/net/artelnatif/nicko/impl/InternalsProvider.java @@ -6,12 +6,17 @@ import java.lang.reflect.InvocationTargetException; public class InternalsProvider { private static Internals internals; + private static boolean protocolLib = true; static { try { final String packageName = Internals.class.getPackage().getName(); final String bukkitVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3]; - final String fullClassName = packageName + "." + bukkitVersion; + String fullClassName = packageName + "." + bukkitVersion; + if (protocolLib) { + System.out.println("USING PROTOCOLLIB HACK"); + fullClassName += "_P"; + } internals = (Internals) Class.forName(fullClassName).getConstructors()[0].newInstance(); } catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException | ClassCastException exception) { diff --git a/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2.java b/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2.java index 81391b5..c6669de 100644 --- a/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2.java +++ b/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2.java @@ -86,12 +86,12 @@ public class v1_19_R2 implements Internals { @Override public ActionResult updateProfile(Player player, NickoProfile profile, boolean skinChange, boolean reset) { final boolean changeOnlyName = profile.getSkin() != null && !profile.getSkin().equalsIgnoreCase(player.getName()); - // TODO: 1/20/23 Unable to update the GameProfile name. - //final String profileName = profile.getName() == null ? player.getName() : profile.getName(); + final String profileName = profile.getName() == null ? player.getName() : profile.getName(); Optional skin; final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); final GameProfile gameProfile = serverPlayer.getGameProfile(); + final GameProfile newGameProfile = new GameProfile(player.getUniqueId(), profileName); final ClientboundPlayerInfoRemovePacket remove = new ClientboundPlayerInfoRemovePacket(List.of(player.getUniqueId())); // TODO: 1/20/23 Sets Gamemode to Survival but keeps the flying? Visual effect only? @@ -104,7 +104,7 @@ public class v1_19_R2 implements Internals { if (uuid.isPresent()) { skin = (reset ? mojang.getSkinWithoutCaching(uuid.get()) : mojang.getSkin(uuid.get())); if (skin.isPresent()) { - final PropertyMap properties = gameProfile.getProperties(); + final PropertyMap properties = newGameProfile.getProperties(); properties.removeAll("textures"); properties.put("textures", new Property("textures", skin.get().value(), skin.get().signature())); updateSelf(player); @@ -121,6 +121,30 @@ public class v1_19_R2 implements Internals { } } + /* + Tried this solution, doesn't work either: + + final FriendlyByteBuf byteBuf = new FriendlyByteBuf(Unpooled.buffer()); + final EnumSet actions = EnumSet.of(ClientboundPlayerInfoUpdatePacket.Action.ADD_PLAYER); + byteBuf.writeEnumSet(actions, ClientboundPlayerInfoUpdatePacket.Action.class); + byteBuf.writeCollection(List.of(new ClientboundPlayerInfoUpdatePacket.Entry( + player.getUniqueId(), + newGameProfile, + true, + serverPlayer.latency, + serverPlayer.gameMode.getGameModeForPlayer(), + Component.literal(profileName), + serverPlayer.getChatSession().asData() + )), (bb, entry) -> { + bb.writeUUID(entry.profileId()); + Iterator iterator = actions.iterator(); + + while (iterator.hasNext()) { + bb.writeUtf(entry.profile().getName(), 16); + bb.writeGameProfileProperties(newGameProfile.getProperties()); + } + });*/ + serverPlayer.connection.send(remove); serverPlayer.connection.send(init); Bukkit.getOnlinePlayers().forEach(online -> { diff --git a/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2_P.java b/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2_P.java new file mode 100644 index 0000000..810c8f2 --- /dev/null +++ b/v1_19_R2/src/main/java/net/artelnatif/nicko/impl/v1_19_R2_P.java @@ -0,0 +1,147 @@ +package net.artelnatif.nicko.impl; + +import com.comphenix.protocol.PacketType; +import com.comphenix.protocol.events.PacketContainer; +import com.comphenix.protocol.wrappers.*; +import com.google.common.collect.Multimap; +import net.artelnatif.nicko.NickoBukkit; +import net.artelnatif.nicko.disguise.ActionResult; +import net.artelnatif.nicko.disguise.NickoProfile; +import net.artelnatif.nicko.i18n.I18NDict; +import net.artelnatif.nicko.mojang.MojangAPI; +import net.artelnatif.nicko.mojang.MojangSkin; +import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; +import net.minecraft.network.protocol.game.ClientboundRemoveEntitiesPacket; +import net.minecraft.network.protocol.game.ClientboundRespawnPacket; +import net.minecraft.network.protocol.game.ClientboundSetEntityDataPacket; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.EntityDataSerializers; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.Level; +import org.bukkit.Bukkit; +import org.bukkit.craftbukkit.v1_19_R2.CraftWorld; +import org.bukkit.craftbukkit.v1_19_R2.entity.CraftPlayer; +import org.bukkit.entity.Player; +import org.bukkit.event.player.PlayerTeleportEvent; + +import java.io.IOException; +import java.util.Collections; +import java.util.EnumSet; +import java.util.Optional; +import java.util.concurrent.ExecutionException; + +public class v1_19_R2_P implements InternalsProtocolLib { + @Override + public void updateSelf(Player player) { + final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + final ServerLevel level = serverPlayer.getLevel(); + final ResourceKey levelResourceKey = serverPlayer.getLevel().dimension(); + final CraftWorld world = level.getWorld(); + // last boolean is: "has death location" attribute, if true, the optional contains the death dimension and position. + // with the boolean being false, we don't need to provide a value, and thus we return an empty optional. + final ClientboundRespawnPacket respawn = new ClientboundRespawnPacket(serverPlayer.level.dimensionTypeId(), + levelResourceKey, world.getSeed(), + serverPlayer.gameMode.getPreviousGameModeForPlayer(), serverPlayer.gameMode.getGameModeForPlayer(), + level.isDebug(), + level.isFlat(), + (byte) 0x00, + Optional.empty()); + + final boolean wasFlying = player.isFlying(); + serverPlayer.connection.send(respawn); + player.setFlying(wasFlying); + player.teleport(player.getLocation(), PlayerTeleportEvent.TeleportCause.PLUGIN); + player.updateInventory(); + } + + @Override + public void updateOthers(Player player) { + final ServerPlayer serverPlayer = ((CraftPlayer) player).getHandle(); + final ClientboundRemoveEntitiesPacket remove = new ClientboundRemoveEntitiesPacket(serverPlayer.getBukkitEntity().getEntityId()); + final ClientboundAddEntityPacket add = new ClientboundAddEntityPacket(serverPlayer); + + /* + BIT MASKS: + 0x01 Cape enabled + 0x02 Jacket enabled + 0x04 Left sleeve enabled + 0x08 Right sleeve enabled + 0x10 Left pants leg enabled + 0x20 Right pants leg enabled + 0x40 Hat enabled + */ + final SynchedEntityData entityData = serverPlayer.getEntityData(); + final EntityDataAccessor skinPartAccessor = new EntityDataAccessor<>(17, EntityDataSerializers.BYTE); + entityData.set(skinPartAccessor, (byte) 0x7f); // 127, all masks combined + final ClientboundSetEntityDataPacket entityMetadata = new ClientboundSetEntityDataPacket(serverPlayer.getBukkitEntity().getEntityId(), entityData.getNonDefaultValues()); + + Bukkit.getOnlinePlayers().forEach(online -> { + ServerPlayer onlineServerPlayer = ((CraftPlayer) online).getHandle(); + if (onlineServerPlayer.getBukkitEntity().getUniqueId() != player.getUniqueId()) { + onlineServerPlayer.connection.send(remove); + onlineServerPlayer.connection.send(add); + } + onlineServerPlayer.connection.send(entityMetadata); + }); + } + + @Override + public ActionResult updateProfile(Player player, NickoProfile profile, boolean skinChange, boolean reset) { + final boolean changeOnlyName = profile.getSkin() != null && !profile.getSkin().equalsIgnoreCase(player.getName()); + final String profileName = profile.getName() == null ? player.getName() : profile.getName(); + Optional skin; + + final WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player).withName(profileName); + if (skinChange || changeOnlyName) { + try { + final MojangAPI mojang = NickoBukkit.getInstance().getMojangAPI(); + final Optional uuid = mojang.getUUID(profile.getSkin()); + if (uuid.isPresent()) { + skin = (reset ? mojang.getSkinWithoutCaching(uuid.get()) : mojang.getSkin(uuid.get())); + if (skin.isPresent()) { + final Multimap properties = gameProfile.getProperties(); + properties.removeAll("textures"); + properties.put("textures", new WrappedSignedProperty("textures", skin.get().value(), skin.get().signature())); + updateSelf(player); + } else { + return new ActionResult(I18NDict.Error.SKIN_FAIL_MOJANG); + } + } else { + return new ActionResult(I18NDict.Error.NAME_FAIL_MOJANG); + } + } catch (ExecutionException e) { + return new ActionResult(I18NDict.Error.SKIN_FAIL_CACHE); + } catch (IOException e) { + return new ActionResult(I18NDict.Error.NAME_FAIL_MOJANG); + } + } + + // Letting ProtocolLib handle the reflection here. + final PacketContainer remove = getProtocolLib().createPacket(PacketType.Play.Server.PLAYER_INFO_REMOVE); + remove.getUUIDLists().write(0, Collections.singletonList(player.getUniqueId())); + + final EnumSet actions = EnumSet.of( + EnumWrappers.PlayerInfoAction.ADD_PLAYER, + EnumWrappers.PlayerInfoAction.UPDATE_LATENCY, + EnumWrappers.PlayerInfoAction.UPDATE_LISTED); + final PacketContainer add = getProtocolLib().createPacket(PacketType.Play.Server.PLAYER_INFO); + + add.getPlayerInfoActions().write(0, actions); + add.getPlayerInfoDataLists().write(1, Collections.singletonList(new PlayerInfoData( + gameProfile, + player.getPing(), + EnumWrappers.NativeGameMode.fromBukkit(player.getGameMode()), + WrappedChatComponent.fromText(profileName) + ))); + + Bukkit.getOnlinePlayers().forEach(online -> { + getProtocolLib().sendServerPacket(online, remove); + getProtocolLib().sendServerPacket(online, add); + }); + updateOthers(player); + return new ActionResult(); + } +} \ No newline at end of file