diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java index b8fa4cc0b..d57cfdf3d 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/BukkitNetworkManager.java @@ -10,7 +10,6 @@ import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.network.impl.*; import net.momirealms.craftengine.bukkit.plugin.user.BukkitServerPlayer; import net.momirealms.craftengine.bukkit.util.Reflections; -import net.momirealms.craftengine.bukkit.util.RegistryUtils; import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; @@ -19,10 +18,7 @@ import net.momirealms.craftengine.core.util.FriendlyByteBuf; import net.momirealms.craftengine.core.util.ListMonitor; import net.momirealms.craftengine.core.util.TriConsumer; import net.momirealms.craftengine.core.util.VersionHelper; -import net.momirealms.craftengine.core.world.ChunkPos; import org.bukkit.Bukkit; -import org.bukkit.Chunk; -import org.bukkit.World; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -30,17 +26,15 @@ import org.bukkit.event.HandlerList; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; -import org.bukkit.event.player.PlayerRegisterChannelEvent; +import org.bukkit.plugin.messaging.PluginMessageListener; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.lang.reflect.InvocationTargetException; -import java.nio.charset.StandardCharsets; import java.util.*; import java.util.concurrent.ConcurrentHashMap; import java.util.function.BiConsumer; -public class BukkitNetworkManager implements NetworkManager, Listener { +public class BukkitNetworkManager implements NetworkManager, Listener, PluginMessageListener { private static BukkitNetworkManager instance; private static final Map, TriConsumer> nmsPacketFunctions = new HashMap<>(); private static final Map> byteBufPacketFunctions = new HashMap<>(); @@ -135,6 +129,7 @@ public class BukkitNetworkManager implements NetworkManager, Listener { registerNMSPacketConsumer(PacketConsumers.RENAME_ITEM, Reflections.clazz$ServerboundRenameItemPacket); registerNMSPacketConsumer(PacketConsumers.SIGN_UPDATE, Reflections.clazz$ServerboundSignUpdatePacket); registerNMSPacketConsumer(PacketConsumers.EDIT_BOOK, Reflections.clazz$ServerboundEditBookPacket); + registerNMSPacketConsumer(PacketConsumers.CUSTOM_PAYLOAD, Reflections.clazz$ServerboundCustomPayloadPacket); registerByteBufPacketConsumer(PacketConsumers.SECTION_BLOCK_UPDATE, this.packetIds.clientboundSectionBlocksUpdatePacket()); registerByteBufPacketConsumer(PacketConsumers.BLOCK_UPDATE, this.packetIds.clientboundBlockUpdatePacket()); registerByteBufPacketConsumer(PacketConsumers.LEVEL_PARTICLE, this.packetIds.clientboundLevelParticlesPacket()); @@ -165,61 +160,21 @@ public class BukkitNetworkManager implements NetworkManager, Listener { this.onlineUsers.remove(player.getUniqueId()); } - // for mod - @EventHandler(priority = EventPriority.LOWEST) - public void onPlayerRegisterChannel(PlayerRegisterChannelEvent event) { - if (!event.getChannel().equals(MOD_CHANNEL)) return; - Player player = event.getPlayer(); - NetWorkUser user = getUser(player); - if (user == null) return; - user.setClientModState(true); - int blockRegistrySize = RegistryUtils.currentBlockRegistrySize(); - byte[] payload = ("cp:" + blockRegistrySize).getBytes(StandardCharsets.UTF_8); - player.sendPluginMessage(plugin.bootstrap(), MOD_CHANNEL, payload); - plugin.scheduler().executeAsync(() -> sendClientModPlayerChunk(player, user)); - } - - private void sendClientModPlayerChunk(Player player, NetWorkUser user) { - try { - Chunk centerChunk = player.getLocation().getChunk(); - World world = centerChunk.getWorld(); - int centerX = centerChunk.getX(); - int centerZ = centerChunk.getZ(); - List packets = new ArrayList<>(); - for (int xOffset = -1; xOffset <= 1; xOffset++) { - for (int zOffset = -1; zOffset <= 1; zOffset++) { - int targetX = centerX + xOffset; - int targetZ = centerZ + zOffset; - if (!world.isChunkLoaded(targetX, targetZ)) continue; - Chunk chunk = world.getChunkAt(targetX, targetZ); - Object worldServer = Reflections.field$CraftChunk$worldServer.get(chunk); - Object chunkSource = Reflections.field$ServerLevel$chunkSource.get(worldServer); - Object levelChunk = Reflections.method$ServerChunkCache$getChunkAtIfLoadedMainThread.invoke(chunkSource, targetX, targetZ); - if (levelChunk == null) continue; - long chunkKey = ChunkPos.asLong(targetX, targetZ); - Object chunkHolder = Reflections.method$ServerChunkCache$getVisibleChunkIfPresent.invoke(chunkSource, chunkKey); - if (chunkHolder == null) continue; - Object lightEngine = Reflections.field$ChunkHolder$lightEngine.get(chunkHolder); - Object packet = Reflections.constructor$ClientboundLevelChunkWithLightPacket.newInstance(levelChunk, lightEngine, null, null); - packets.add(packet); - } - } - sendPackets(user, packets); - } catch (Exception e) { - CraftEngine.instance().logger().warn("Failed to send chunk correction packet", e); - } - } - @Override public Collection onlineUsers() { return onlineUsers.values(); } + @Override + // 保留仅注册入频道用 + public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, byte @NotNull [] message) {} + @Override public void init() { if (init) return; try { - plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(plugin.bootstrap(), "craftengine:payload"); + plugin.bootstrap().getServer().getMessenger().registerIncomingPluginChannel(plugin.bootstrap(), MOD_CHANNEL, this); + plugin.bootstrap().getServer().getMessenger().registerOutgoingPluginChannel(plugin.bootstrap(), MOD_CHANNEL); Object server = Reflections.method$MinecraftServer$getServer.invoke(null); Object serverConnection = Reflections.field$MinecraftServer$connection.get(server); @SuppressWarnings("unchecked") diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java index aa2dbd8f2..a220f5f1b 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/network/PacketConsumers.java @@ -3,6 +3,8 @@ package net.momirealms.craftengine.bukkit.plugin.network; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.ints.IntList; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.TranslationArgument; import net.momirealms.craftengine.bukkit.api.CraftEngineFurniture; import net.momirealms.craftengine.bukkit.api.event.FurnitureBreakEvent; import net.momirealms.craftengine.bukkit.api.event.FurnitureInteractEvent; @@ -19,6 +21,7 @@ import net.momirealms.craftengine.core.plugin.CraftEngine; import net.momirealms.craftengine.core.plugin.config.ConfigManager; import net.momirealms.craftengine.core.plugin.network.ConnectionState; import net.momirealms.craftengine.core.plugin.network.NetWorkUser; +import net.momirealms.craftengine.core.plugin.network.NetworkManager; import net.momirealms.craftengine.core.util.*; import net.momirealms.craftengine.core.world.BlockPos; import net.momirealms.craftengine.core.world.chunk.Palette; @@ -33,6 +36,7 @@ import org.bukkit.util.RayTraceResult; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.nio.charset.StandardCharsets; import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -896,4 +900,43 @@ public class PacketConsumers { callback.accept(new String(newCodepoints, 0, newCodepoints.length)); } } + public static final TriConsumer CUSTOM_PAYLOAD = (user, event, packet) -> { + try { + if (!VersionHelper.isVersionNewerThan1_20_5()) return; + Object payload = Reflections.field$ServerboundCustomPayloadPacket$payload.get(packet); + if (payload.getClass().equals(Reflections.clazz$DiscardedPayload)) { + Object type = Reflections.method$CustomPacketPayload$type.invoke(payload); + Object id = Reflections.method$CustomPacketPayload$Type$id.invoke(type); + String channel = id.toString(); + if (!channel.equals(NetworkManager.MOD_CHANNEL)) return; + ByteBuf buf = (ByteBuf) Reflections.method$DiscardedPayload$data.invoke(payload); + byte[] data = new byte[buf.readableBytes()]; + buf.readBytes(data); + String decodeData = new String(data, StandardCharsets.UTF_8); + if (!decodeData.endsWith("init")) return; + int firstColon = decodeData.indexOf(':'); + if (firstColon == -1) return; + int secondColon = decodeData.indexOf(':', firstColon + 1); + if (secondColon == -1) return; + int clientBlockRegistrySize = Integer.parseInt(decodeData.substring(firstColon + 1, secondColon)); + int serverBlockRegistrySize = RegistryUtils.currentBlockRegistrySize(); + if (clientBlockRegistrySize != serverBlockRegistrySize) { + Object kickPacket = Reflections.constructor$ClientboundDisconnectPacket.newInstance( + ComponentUtils.adventureToMinecraft( + Component.translatable( + "disconnect.craftengine.block_registry_mismatch", + TranslationArgument.numeric(clientBlockRegistrySize), + TranslationArgument.numeric(serverBlockRegistrySize) + ) + ) + ); + user.nettyChannel().writeAndFlush(kickPacket); + user.nettyChannel().disconnect(); + } + user.setClientModState(true); + } + } catch (Exception e) { + CraftEngine.instance().logger().warn("Failed to handle ServerboundCustomPayloadPacket", e); + } + }; } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java index 41d8af579..82b91b1db 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/Reflections.java @@ -5540,4 +5540,62 @@ public class Reflections { ? ReflectionUtils.getMethod(clazz$BlockBehaviour, String.class) : ReflectionUtils.getMethod(clazz$Block, String.class) ); + + public static final Class clazz$ServerboundCustomPayloadPacket = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.common.ServerboundCustomPayloadPacket"), + BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayInCustomPayload") + ) + ); + + // 1.20.2+ + public static final Class clazz$CustomPacketPayload = + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.common.custom.CustomPacketPayload") + ); + + // 1.20.5+ + public static final Class clazz$CustomPacketPayload$Type = + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.common.custom.CustomPacketPayload$Type") + ); + + // 1.20.2+ + public static final Field field$ServerboundCustomPayloadPacket$payload = Optional.ofNullable(clazz$CustomPacketPayload) + .map(it -> ReflectionUtils.getDeclaredField(clazz$ServerboundCustomPayloadPacket, it, 0)) + .orElse(null); + + // 1.20.2+ + public static final Class clazz$DiscardedPayload = + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.common.custom.DiscardedPayload") + ); + + // 1.20.5+ + public static final Method method$CustomPacketPayload$type = Optional.ofNullable(clazz$CustomPacketPayload$Type) + .map(it -> ReflectionUtils.getMethod(clazz$CustomPacketPayload, it)) + .orElse(null); + + // 1.20.5+ + public static final Method method$CustomPacketPayload$Type$id = Optional.ofNullable(clazz$CustomPacketPayload$Type) + .map(it -> ReflectionUtils.getMethod(it, clazz$ResourceLocation)) + .orElse(null); + + // 1.20.5+ + public static final Method method$DiscardedPayload$data = Optional.ofNullable(clazz$DiscardedPayload) + .map(it -> ReflectionUtils.getMethod(it, ByteBuf.class)) + .orElse(null); + + public static final Class clazz$ClientboundDisconnectPacket = requireNonNull( + ReflectionUtils.getClazz( + BukkitReflectionUtils.assembleMCClass("network.protocol.common.ClientboundDisconnectPacket"), + BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutKickDisconnect") + ) + ); + + public static final Constructor constructor$ClientboundDisconnectPacket = requireNonNull( + ReflectionUtils.getConstructor( + clazz$ClientboundDisconnectPacket, clazz$Component + ) + ); } diff --git a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/CraftEngineFabricModClient.java b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/CraftEngineFabricModClient.java index f0ab3bcf0..d3bdbf562 100644 --- a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/CraftEngineFabricModClient.java +++ b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/CraftEngineFabricModClient.java @@ -2,38 +2,33 @@ package net.momirealms.craftengine.fabric.client; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.blockrenderlayer.v1.BlockRenderLayerMap; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayConnectionEvents; -import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationConnectionEvents; +import net.fabricmc.fabric.api.client.networking.v1.ClientConfigurationNetworking; import net.fabricmc.fabric.api.client.rendering.v1.ColorProviderRegistry; import net.fabricmc.fabric.api.networking.v1.PayloadTypeRegistry; -import net.fabricmc.loader.api.FabricLoader; import net.minecraft.block.Block; import net.minecraft.client.MinecraftClient; import net.minecraft.client.color.world.BiomeColors; -import net.minecraft.client.network.ClientPlayNetworkHandler; +import net.minecraft.client.network.ClientConfigurationNetworkHandler; import net.minecraft.client.render.RenderLayer; -import net.minecraft.network.DisconnectionInfo; import net.minecraft.registry.Registries; -import net.minecraft.text.Text; import net.minecraft.util.Identifier; import net.minecraft.world.biome.FoliageColors; import net.momirealms.craftengine.fabric.client.blocks.CustomBlock; import net.momirealms.craftengine.fabric.client.config.ModConfig; import net.momirealms.craftengine.fabric.client.network.CraftEnginePayload; -import java.net.URI; import java.nio.charset.StandardCharsets; -import java.util.Optional; public class CraftEngineFabricModClient implements ClientModInitializer { public static final String MOD_ID = "craftengine"; @Override public void onInitializeClient() { - PayloadTypeRegistry.playS2C().register(CraftEnginePayload.ID, CraftEnginePayload.CODEC); - initChannel(MinecraftClient.getInstance().getNetworkHandler()); + PayloadTypeRegistry.configurationS2C().register(CraftEnginePayload.ID, CraftEnginePayload.CODEC); + PayloadTypeRegistry.configurationC2S().register(CraftEnginePayload.ID, CraftEnginePayload.CODEC); registerRenderLayer(); - ClientPlayConnectionEvents.INIT.register((handler, client) -> initChannel(handler)); + ClientConfigurationConnectionEvents.START.register(CraftEngineFabricModClient::initChannel); } public static void registerRenderLayer() { @@ -62,30 +57,15 @@ public class CraftEngineFabricModClient implements ClientModInitializer { ); } - private static void initChannel(ClientPlayNetworkHandler handler) { + private static void initChannel(ClientConfigurationNetworkHandler handler, MinecraftClient client) { if (ModConfig.enableNetwork) { registerChannel(handler); } else { - ClientPlayNetworking.unregisterGlobalReceiver(CraftEnginePayload.ID.id()); + ClientConfigurationNetworking.unregisterGlobalReceiver(CraftEnginePayload.ID); } } - private static void registerChannel(ClientPlayNetworkHandler handler) { - ClientPlayNetworking.registerGlobalReceiver(CraftEnginePayload.ID, (payload, context) -> { - byte[] data = payload.data(); - String decoded = new String(data, StandardCharsets.UTF_8); - if (decoded.startsWith("cp:")) { - int blockRegistrySize = Integer.parseInt(decoded.substring(3)); - if (Block.STATE_IDS.size() != blockRegistrySize) { - handler.getConnection().disconnect( - new DisconnectionInfo( - Text.translatable("disconnect.craftengine.block_registry_mismatch", Block.STATE_IDS.size(), blockRegistrySize), - Optional.of(FabricLoader.getInstance().getConfigDir().resolve("craft-engine-fabric-mod/mappings.yml")), - Optional.of(URI.create("https://github.com/Xiao-MoMi/craft-engine")) - ) - ); - } - } - }); + private static void registerChannel(ClientConfigurationNetworkHandler handler) { + ClientConfigurationNetworking.send(new CraftEnginePayload((":" + Block.STATE_IDS.size() + ":init").getBytes(StandardCharsets.UTF_8))); } } diff --git a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/network/CraftEnginePayload.java b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/network/CraftEnginePayload.java index eb8e5add2..c975c1f7f 100644 --- a/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/network/CraftEnginePayload.java +++ b/client-mod/src/client/java/net/momirealms/craftengine/fabric/client/network/CraftEnginePayload.java @@ -1,6 +1,6 @@ package net.momirealms.craftengine.fabric.client.network; -import net.minecraft.network.RegistryByteBuf; +import net.minecraft.network.PacketByteBuf; import net.minecraft.network.codec.PacketCodec; import net.minecraft.network.packet.CustomPayload; import net.minecraft.util.Identifier; @@ -8,10 +8,9 @@ import net.minecraft.util.Identifier; public record CraftEnginePayload(byte[] data) implements CustomPayload { public static final Identifier CRAFTENGINE_PAYLOAD = Identifier.of("craftengine", "payload"); public static final Id ID = new Id<>(CraftEnginePayload.CRAFTENGINE_PAYLOAD); - public static final PacketCodec CODEC = PacketCodec.tuple( - new ByteArrayCodec(), CraftEnginePayload::data, - CraftEnginePayload::new - ); + public static final PacketCodec CODEC = PacketCodec.of( + (payload, byteBuf) -> byteBuf.writeByteArray(payload.data()), + buf -> new CraftEnginePayload(buf.readByteArray())); @Override public Id getId() {