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