diff --git a/patches/net/minecraft/client/Minecraft.java.patch b/patches/net/minecraft/client/Minecraft.java.patch index b4e365897d..9354f70328 100644 --- a/patches/net/minecraft/client/Minecraft.java.patch +++ b/patches/net/minecraft/client/Minecraft.java.patch @@ -387,18 +387,10 @@ try { this.updateScreenAndTick(p_91321_); if (this.level != null) { -+ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.level.LevelEvent.Unload(this.level)); ++ net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.level.LevelEvent.Unload(this.level)); if (integratedserver != null) { this.profiler.push("waitForServer"); -@@ -2176,6 +_,7 @@ - - this.gui.onDisconnected(); - this.isLocalServer = false; -+ net.neoforged.neoforge.client.ClientHooks.handleClientLevelClosing(this.level); - } - - this.level = null; @@ -2324,6 +_,7 @@ private void pickBlock() { diff --git a/patches/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch b/patches/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch deleted file mode 100644 index 37bd0bd005..0000000000 --- a/patches/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java.patch +++ /dev/null @@ -1,11 +0,0 @@ ---- a/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java -+++ b/net/minecraft/client/gui/screens/multiplayer/ServerSelectionList.java -@@ -381,6 +_,8 @@ - this.screen.setToolTip(list1); - } - -+ net.neoforged.neoforge.client.ClientHooks.drawForgePingInfo(this.screen, serverData, p_281406_, p_281363_, p_282921_, p_283596_, l, i1); -+ - if (this.minecraft.options.touchscreen().get() || p_282999_) { - p_281406_.fill(p_281363_, p_282921_, p_281363_ + 32, p_282921_ + 32, -1601138544); - int j1 = p_283567_ - p_281363_; diff --git a/patches/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch b/patches/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch index c9e9bf42c7..f3cd943c3b 100644 --- a/patches/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch +++ b/patches/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch @@ -1,6 +1,12 @@ --- a/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java +++ b/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java -@@ -101,12 +_,12 @@ +@@ -96,17 +_,17 @@ + public void handleCustomPayload(ClientboundCustomPayloadPacket p_295727_) { + CustomPacketPayload custompacketpayload = p_295727_.payload(); + if (!(custompacketpayload instanceof DiscardedPayload)) { +- PacketUtils.ensureRunningOnSameThread(p_295727_, this, this.minecraft); + if (custompacketpayload instanceof BrandPayload brandpayload) { ++ PacketUtils.ensureRunningOnSameThread(p_295727_, this, this.minecraft); //Neo: We move this here to ensure that only vanilla packets are handled on the main thread. this.serverBrand = brandpayload.brand(); this.telemetryManager.onServerBrandReceived(brandpayload.brand()); } else { @@ -15,3 +21,40 @@ protected abstract RegistryAccess.Frozen registryAccess(); +@@ -189,6 +_,10 @@ + } + + public void send(Packet p_295097_) { ++ if (!net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().canSendPacket(p_295097_, this)) { ++ return; ++ } ++ + this.connection.send(p_295097_); + } + +@@ -196,6 +_,9 @@ + public void onDisconnect(Component p_295485_) { + this.telemetryManager.onDisconnect(); + this.minecraft.disconnect(this.createDisconnectScreen(p_295485_)); ++ if (!this.connection.isMemoryConnection()) { ++ net.neoforged.neoforge.registries.RegistryManager.revertToFrozen(); ++ } + LOGGER.warn("Client disconnected with reason: {}", p_295485_.getString()); + } + +@@ -315,5 +_,15 @@ + @OnlyIn(Dist.CLIENT) + static record PendingRequest(UUID id, URL url, String hash) { + } ++ } ++ ++ @Override ++ public Connection getConnection() { ++ return connection; ++ } ++ ++ @Override ++ public Minecraft getMinecraft() { ++ return minecraft; + } + } diff --git a/patches/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java.patch b/patches/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java.patch index f8ee03b81e..c5152967b4 100644 --- a/patches/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java.patch +++ b/patches/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java.patch @@ -1,29 +1,81 @@ --- a/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java +++ b/net/minecraft/client/multiplayer/ClientConfigurationPacketListenerImpl.java -@@ -45,11 +_,15 @@ +@@ -26,6 +_,9 @@ + private final GameProfile localGameProfile; + private RegistryAccess.Frozen receivedRegistries; + private FeatureFlagSet enabledFeatures; ++ private boolean isModdedConnection = false; ++ private boolean isVanillaConnection = true; ++ private java.util.Map failureReasons = new java.util.HashMap<>(); + + public ClientConfigurationPacketListenerImpl(Minecraft p_295262_, Connection p_296339_, CommonListenerCookie p_294706_) { + super(p_295262_, p_296339_, p_294706_); +@@ -45,7 +_,30 @@ } @Override - protected void handleCustomPayload(CustomPacketPayload p_295411_) { -- this.handleUnknownCustomPayload(p_295411_); + protected void handleCustomPayload(net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket p_295727_, CustomPacketPayload p_295411_) { -+ this.handleUnknownCustomPayload(p_295727_, p_295411_); - } - -- private void handleUnknownCustomPayload(CustomPacketPayload p_296412_) { -+ private void handleUnknownCustomPayload(net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket p_295727_, CustomPacketPayload p_296412_) { -+ if (p_296412_ instanceof net.neoforged.neoforge.network.custom.payload.SimplePayload simplePayload && net.neoforged.neoforge.network.NetworkHooks.onCustomPayload(p_295727_, simplePayload, connection)) { ++ if (p_295411_ instanceof net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload) { ++ this.isModdedConnection = true; ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onNetworkQuery(this); ++ return; ++ } ++ if (p_295411_ instanceof net.neoforged.neoforge.network.payload.ModdedNetworkPayload moddedNetworkPayload) { ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onModdedNetworkConnectionEstablished(this, moddedNetworkPayload.configuration(), moddedNetworkPayload.play()); ++ return; ++ } ++ if (p_295411_ instanceof net.neoforged.neoforge.network.payload.ModdedNetworkSetupFailedPayload setupFailedPayload) { ++ failureReasons = setupFailedPayload.failureReasons(); ++ } ++ if (!this.isModdedConnection && p_295411_ instanceof net.minecraft.network.protocol.common.custom.BrandPayload) { ++ this.isVanillaConnection = true; ++ if (!net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onVanillaNetworkConnectionEstablished(this)) { ++ return; ++ } ++ } ++ if (this.isModdedConnection) { ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onModdedPacketAtClient(this, p_295727_); + return; + } + - LOGGER.warn("Unknown custom packet payload: {}", p_296412_.id()); + this.handleUnknownCustomPayload(p_295411_); } -@@ -93,6 +_,7 @@ - ); - this.connection.resumeInboundAfterProtocolChange(); - this.connection.send(new ServerboundFinishConfigurationPacket()); -+ net.neoforged.neoforge.network.NetworkHooks.handleClientLoginSuccess(this.connection); +@@ -69,6 +_,11 @@ + @Override + public void handleEnabledFeatures(ClientboundUpdateEnabledFeaturesPacket p_294410_) { + this.enabledFeatures = FeatureFlags.REGISTRY.fromNames(p_294410_.features()); ++ //Fallback detection layer for vanilla servers ++ if (!this.isModdedConnection) { ++ this.isVanillaConnection = true; ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onVanillaNetworkConnectionEstablished(this); ++ } } @Override +@@ -87,7 +_,8 @@ + this.enabledFeatures, + this.serverBrand, + this.serverData, +- this.postDisconnectScreen ++ this.postDisconnectScreen, ++ this.isModdedConnection + ) + ) + ); +@@ -104,5 +_,14 @@ + public void onDisconnect(Component p_314649_) { + super.onDisconnect(p_314649_); + this.minecraft.clearDownloadedResourcePacks(); ++ } ++ ++ @Override ++ protected net.minecraft.client.gui.screens.Screen createDisconnectScreen(net.minecraft.network.chat.Component p_296470_) { ++ final net.minecraft.client.gui.screens.Screen superScreen = super.createDisconnectScreen(p_296470_); ++ if (failureReasons.isEmpty()) ++ return superScreen; ++ ++ return new net.neoforged.neoforge.client.gui.ModMismatchDisconnectedScreen(superScreen, p_296470_, failureReasons); + } + } diff --git a/patches/net/minecraft/client/multiplayer/ClientHandshakePacketListenerImpl.java.patch b/patches/net/minecraft/client/multiplayer/ClientHandshakePacketListenerImpl.java.patch deleted file mode 100644 index e097c4a784..0000000000 --- a/patches/net/minecraft/client/multiplayer/ClientHandshakePacketListenerImpl.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/net/minecraft/client/multiplayer/ClientHandshakePacketListenerImpl.java -+++ b/net/minecraft/client/multiplayer/ClientHandshakePacketListenerImpl.java -@@ -181,7 +_,7 @@ - if (this.serverData != null && this.serverData.isRealm()) { - this.minecraft.setScreen(new DisconnectedRealmsScreen(this.parent, CommonComponents.CONNECT_FAILED, p_104543_)); - } else { -- this.minecraft.setScreen(new DisconnectedScreen(this.parent, CommonComponents.CONNECT_FAILED, p_104543_)); -+ this.minecraft.setScreen(net.neoforged.neoforge.network.NetworkHooks.getModMismatchData(connection) != null ? new net.neoforged.neoforge.client.gui.ModMismatchDisconnectedScreen(this.parent, CommonComponents.CONNECT_FAILED, p_104543_, net.neoforged.neoforge.network.NetworkHooks.getModMismatchData(connection)) : new DisconnectedScreen(this.parent, CommonComponents.CONNECT_FAILED, p_104543_)); - } - } - -@@ -204,6 +_,7 @@ - - @Override - public void handleCustomQuery(ClientboundCustomQueryPacket p_104545_) { -+ if (p_104545_.payload() instanceof net.neoforged.neoforge.network.ICustomQueryPayloadWithBuffer payload && net.neoforged.neoforge.network.NetworkHooks.onCustomQuery(p_104545_, payload, this.connection)) return; - this.updateStatus.accept(Component.translatable("connect.negotiating")); - this.connection.send(new ServerboundCustomQueryAnswerPacket(p_104545_.transactionId(), null)); - } diff --git a/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch b/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch index e481888f0b..fb58bd606c 100644 --- a/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch +++ b/patches/net/minecraft/client/multiplayer/ClientPacketListener.java.patch @@ -1,5 +1,21 @@ --- a/net/minecraft/client/multiplayer/ClientPacketListener.java +++ b/net/minecraft/client/multiplayer/ClientPacketListener.java +@@ -340,6 +_,7 @@ + private MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final ChunkBatchSizeCalculator chunkBatchSizeCalculator = new ChunkBatchSizeCalculator(); + private final PingDebugMonitor pingDebugMonitor; ++ private final boolean isModdedConnection; + @Nullable + private LevelLoadStatusManager levelLoadStatusManager; + private boolean seenInsecureChatWarning = false; +@@ -353,6 +_,7 @@ + this.advancements = new ClientAdvancements(p_253924_, this.telemetryManager); + this.suggestionsProvider = new ClientSuggestionProvider(this, p_253924_); + this.pingDebugMonitor = new PingDebugMonitor(this, p_253924_.getDebugOverlay().getPingLogger()); ++ this.isModdedConnection = p_295121_.isModdedConnection(); + } + + public ClientSuggestionProvider getSuggestionsProvider() { @@ -416,6 +_,7 @@ this.minecraft.debugRenderer.clear(); @@ -8,14 +24,16 @@ this.minecraft.player.setId(p_105030_.playerId()); this.level.addEntity(this.minecraft.player); this.minecraft.player.input = new KeyboardInput(this.minecraft.options); -@@ -429,6 +_,7 @@ - this.minecraft.player.setPortalCooldown(commonplayerspawninfo.portalCooldown()); - this.minecraft.gameMode.setLocalMode(commonplayerspawninfo.gameType(), commonplayerspawninfo.previousGameType()); - this.minecraft.options.setServerRenderDistance(p_105030_.chunkRadius()); -+ net.neoforged.neoforge.network.NetworkHooks.sendMCRegistryPackets(connection, net.neoforged.neoforge.network.PlayNetworkDirection.PLAY_TO_SERVER); - this.chatSession = null; - this.lastSeenMessages = new LastSeenMessagesTracker(20); - this.messageSignatureCache = MessageSignatureCache.createDefault(); +@@ -801,7 +_,8 @@ + this.enabledFeatures, + this.serverBrand, + this.serverData, +- this.postDisconnectScreen ++ this.postDisconnectScreen, ++ this.isModdedConnection + ) + ) + ); @@ -1143,6 +_,7 @@ } @@ -65,33 +83,28 @@ } @Override -@@ -1916,7 +_,7 @@ +@@ -1916,7 +_,11 @@ } @Override - public void handleCustomPayload(CustomPacketPayload p_295851_) { + public void handleCustomPayload(net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket p_295727_, CustomPacketPayload p_295851_) { ++ if (this.isModdedConnection && net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onModdedPacketAtClient(this, p_295727_)) { ++ return; ++ } ++ PacketUtils.ensureRunningOnSameThread(p_295727_, this, this.minecraft); //Neo: We move this here to ensure that only vanilla packets are handled on the main thread. if (p_295851_ instanceof PathfindingDebugPayload pathfindingdebugpayload) { this.minecraft .debugRenderer -@@ -1985,11 +_,15 @@ - } else if (p_295851_ instanceof BreezeDebugPayload breezedebugpayload) { - this.minecraft.debugRenderer.breezeDebugRenderer.add(breezedebugpayload.breezeInfo()); - } else { -- this.handleUnknownCustomPayload(p_295851_); -+ this.handleUnknownCustomPayload(p_295727_, p_295851_); - } - } +@@ -2267,7 +_,7 @@ + public void handleBundlePacket(ClientboundBundlePacket p_265195_) { + PacketUtils.ensureRunningOnSameThread(p_265195_, this, this.minecraft); -- private void handleUnknownCustomPayload(CustomPacketPayload p_294389_) { -+ private void handleUnknownCustomPayload(net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket p_295727_, CustomPacketPayload p_294389_) { -+ if (p_294389_ instanceof net.neoforged.neoforge.network.custom.payload.SimplePayload simplePayload && net.neoforged.neoforge.network.NetworkHooks.onCustomPayload(p_295727_, simplePayload, connection)) { -+ return; -+ } -+ - LOGGER.warn("Unknown custom packet payload: {}", p_294389_.id()); +- for(Packet packet : p_265195_.subPackets()) { ++ for(Packet packet : p_265195_.subPackets()) { + packet.handle(this); + } } - @@ -2389,6 +_,8 @@ } diff --git a/patches/net/minecraft/client/multiplayer/CommonListenerCookie.java.patch b/patches/net/minecraft/client/multiplayer/CommonListenerCookie.java.patch new file mode 100644 index 0000000000..598721c670 --- /dev/null +++ b/patches/net/minecraft/client/multiplayer/CommonListenerCookie.java.patch @@ -0,0 +1,19 @@ +--- a/net/minecraft/client/multiplayer/CommonListenerCookie.java ++++ b/net/minecraft/client/multiplayer/CommonListenerCookie.java +@@ -17,6 +_,15 @@ + FeatureFlagSet enabledFeatures, + @Nullable String serverBrand, + @Nullable ServerData serverData, +- @Nullable Screen postDisconnectScreen ++ @Nullable Screen postDisconnectScreen, ++ boolean isModdedConnection + ) { ++ ++ /** ++ * @deprecated Use {@link #CommonListenerCookie(GameProfile, WorldSessionTelemetryManager, RegistryAccess.Frozen, FeatureFlagSet, String, ServerData, Screen, boolean)} instead, to indicate whether the connection is modded. ++ */ ++ @Deprecated ++ public CommonListenerCookie(GameProfile localGameProfile, WorldSessionTelemetryManager telemetryManager, RegistryAccess.Frozen receivedRegistries, FeatureFlagSet enabledFeatures, @Nullable String serverBrand, @Nullable ServerData serverData, @Nullable Screen postDisconnectScreen) { ++ this(localGameProfile, telemetryManager, receivedRegistries, enabledFeatures, serverBrand, serverData, postDisconnectScreen, false); ++ } + } diff --git a/patches/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch b/patches/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch deleted file mode 100644 index e2bc5d1022..0000000000 --- a/patches/net/minecraft/client/multiplayer/ServerStatusPinger.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/net/minecraft/client/multiplayer/ServerStatusPinger.java -+++ b/net/minecraft/client/multiplayer/ServerStatusPinger.java -@@ -99,6 +_,7 @@ - p_105461_.run(); - } - }); -+ net.neoforged.neoforge.client.ClientHooks.processForgeListPingData(serverstatus, p_105460_); - this.pingStart = Util.getMillis(); - connection.send(new ServerboundPingRequestPacket(this.pingStart)); - this.success = true; diff --git a/patches/net/minecraft/network/Connection.java.patch b/patches/net/minecraft/network/Connection.java.patch index 286d11126c..fbaa112ca6 100644 --- a/patches/net/minecraft/network/Connection.java.patch +++ b/patches/net/minecraft/network/Connection.java.patch @@ -1,21 +1,21 @@ --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java -@@ -97,6 +_,7 @@ - private volatile Component delayedDisconnect; - @Nullable - BandwidthDebugMonitor bandwidthDebugMonitor; -+ private java.util.function.Consumer activationHandler; - - public Connection(PacketFlow p_129482_) { - this.receiving = p_129482_; -@@ -107,6 +_,7 @@ - super.channelActive(p_129525_); - this.channel = p_129525_.channel(); - this.address = this.channel.remoteAddress(); -+ if (activationHandler != null) activationHandler.accept(this); +@@ -110,6 +_,7 @@ if (this.delayedDisconnect != null) { this.disconnect(this.delayedDisconnect); } ++ net.neoforged.neoforge.network.connection.ConnectionUtils.setConnection(p_129525_, this); + } + + public static void setInitialProtocolAttributes(Channel p_294962_) { +@@ -120,6 +_,7 @@ + @Override + public void channelInactive(ChannelHandlerContext p_129527_) { + this.disconnect(Component.translatable("disconnect.endOfStream")); ++ net.neoforged.neoforge.network.connection.ConnectionUtils.removeConnection(p_129527_); + } + + @Override @@ -380,7 +_,7 @@ if (this.address == null) { return "local"; @@ -25,23 +25,14 @@ } } -@@ -419,6 +_,8 @@ +@@ -419,6 +_,7 @@ } public static ChannelFuture connect(InetSocketAddress p_290034_, boolean p_290035_, final Connection p_290031_) { + net.neoforged.neoforge.network.DualStackUtils.checkIPv6(p_290034_.getAddress()); -+ p_290031_.activationHandler = net.neoforged.neoforge.network.NetworkHooks::registerClientLoginChannel; Class oclass; EventLoopGroup eventloopgroup; if (Epoll.isAvailable() && p_290035_) { -@@ -475,6 +_,7 @@ - - public static Connection connectToLocalServer(SocketAddress p_129494_) { - final Connection connection = new Connection(PacketFlow.CLIENTBOUND); -+ connection.activationHandler = net.neoforged.neoforge.network.NetworkHooks::registerClientLoginChannel; - new Bootstrap().group(LOCAL_WORKER_GROUP.get()).handler(new ChannelInitializer() { - @Override - protected void initChannel(Channel p_129557_) { @@ -569,6 +_,14 @@ public float getAverageSentPackets() { diff --git a/patches/net/minecraft/network/ConnectionProtocol.java.patch b/patches/net/minecraft/network/ConnectionProtocol.java.patch new file mode 100644 index 0000000000..e4b0b84be5 --- /dev/null +++ b/patches/net/minecraft/network/ConnectionProtocol.java.patch @@ -0,0 +1,182 @@ +--- a/net/minecraft/network/ConnectionProtocol.java ++++ b/net/minecraft/network/ConnectionProtocol.java +@@ -243,7 +_,7 @@ + .addPacket(ClientboundContainerSetSlotPacket.class, ClientboundContainerSetSlotPacket::new) + .addPacket(ClientboundCooldownPacket.class, ClientboundCooldownPacket::new) + .addPacket(ClientboundCustomChatCompletionsPacket.class, ClientboundCustomChatCompletionsPacket::new) +- .addPacket(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket::new) ++ .addContextualPacket(ClientboundCustomPayloadPacket.class, (buf, context) -> new ClientboundCustomPayloadPacket(buf, context, ConnectionProtocol.play())) + .addPacket(ClientboundDamageEventPacket.class, ClientboundDamageEventPacket::new) + .addPacket(ClientboundDeleteChatPacket.class, ClientboundDeleteChatPacket::new) + .addPacket(ClientboundDisconnectPacket.class, ClientboundDisconnectPacket::new) +@@ -356,7 +_,7 @@ + .addPacket(ServerboundContainerClickPacket.class, ServerboundContainerClickPacket::new) + .addPacket(ServerboundContainerClosePacket.class, ServerboundContainerClosePacket::new) + .addPacket(ServerboundContainerSlotStateChangedPacket.class, ServerboundContainerSlotStateChangedPacket::new) +- .addPacket(ServerboundCustomPayloadPacket.class, ServerboundCustomPayloadPacket::new) ++ .addContextualPacket(ServerboundCustomPayloadPacket.class, (buf, context) -> new ServerboundCustomPayloadPacket(buf, context, ConnectionProtocol.play())) + .addPacket(ServerboundEditBookPacket.class, ServerboundEditBookPacket::new) + .addPacket(ServerboundEntityTagQuery.class, ServerboundEntityTagQuery::new) + .addPacket(ServerboundInteractPacket.class, ServerboundInteractPacket::new) +@@ -440,7 +_,7 @@ + .addFlow( + PacketFlow.CLIENTBOUND, + new ConnectionProtocol.PacketSet() +- .addPacket(ClientboundCustomPayloadPacket.class, ClientboundCustomPayloadPacket::new) ++ .addContextualPacket(ClientboundCustomPayloadPacket.class, (buf, context) -> new ClientboundCustomPayloadPacket(buf, context, ConnectionProtocol.configuration())) + .addPacket(ClientboundDisconnectPacket.class, ClientboundDisconnectPacket::new) + .addPacket(ClientboundFinishConfigurationPacket.class, ClientboundFinishConfigurationPacket::new) + .addPacket(ClientboundKeepAlivePacket.class, ClientboundKeepAlivePacket::new) +@@ -455,7 +_,7 @@ + PacketFlow.SERVERBOUND, + new ConnectionProtocol.PacketSet() + .addPacket(ServerboundClientInformationPacket.class, ServerboundClientInformationPacket::new) +- .addPacket(ServerboundCustomPayloadPacket.class, ServerboundCustomPayloadPacket::new) ++ .addContextualPacket(ServerboundCustomPayloadPacket.class, (buf, context) -> new ServerboundCustomPayloadPacket(buf, context, ConnectionProtocol.configuration())) + .addPacket(ServerboundFinishConfigurationPacket.class, ServerboundFinishConfigurationPacket::new) + .addPacket(ServerboundKeepAlivePacket.class, ServerboundKeepAlivePacket::new) + .addPacket(ServerboundPongPacket.class, ServerboundPongPacket::new) +@@ -466,7 +_,23 @@ + public static final int NOT_REGISTERED = -1; + private final String id; + private final Map> flows; +- ++ ++ private static ConnectionProtocol play() { ++ return PLAY; ++ } ++ ++ private static ConnectionProtocol configuration() { ++ return CONFIGURATION; ++ } ++ ++ public boolean isPlay() { ++ return this == PLAY; ++ } ++ ++ public boolean isConfiguration() { ++ return this == CONFIGURATION; ++ } ++ + private static ConnectionProtocol.ProtocolBuilder protocol() { + return new ConnectionProtocol.ProtocolBuilder(); + } +@@ -523,11 +_,28 @@ + this.packetSet.classToId.forEach((p_294840_, p_295455_) -> int2objectmap.put(p_295455_.intValue(), p_294840_)); + return int2objectmap; + } +- ++ ++ /** ++ * @deprecated Use {@link #createPacket(int, FriendlyByteBuf, io.netty.channel.ChannelHandlerContext)} instead, which provides the channel context for creating custom packet payloads. ++ */ + @Nullable ++ @Deprecated + public Packet createPacket(int p_294972_, FriendlyByteBuf p_296217_) { + return this.packetSet.createPacket(p_294972_, p_296217_); + } ++ ++ /** ++ * Creates a new packet from the discriminator and the buffer. ++ * ++ * @param p_294972_ The discriminator ++ * @param p_296217_ The buffer ++ * @param p_130535_ The channel context ++ * @return The packet ++ */ ++ @Nullable ++ public Packet createPacket(int p_294972_, FriendlyByteBuf p_296217_, io.netty.channel.ChannelHandlerContext p_130535_) { ++ return this.packetSet.createPacket(p_294972_, p_296217_, p_130535_); ++ } + + public boolean isValidPacketType(Packet p_294142_) { + return this.packetSet.isKnownPacket(p_294142_.getClass()); +@@ -539,12 +_,22 @@ + final Object2IntMap>> classToId = Util.make( + new Object2IntOpenHashMap<>(), p_129613_ -> p_129613_.defaultReturnValue(-1) + ); ++ /** ++ * @deprecated Use {@link #contextualIdToDeserializer} instead it allows for context to be passed to the deserializer ++ */ ++ @Deprecated + private final List>> idToDeserializer = Lists.newArrayList(); ++ private final List>> contextualIdToDeserializer = Lists.newArrayList(); + private BundlerInfo bundlerInfo = BundlerInfo.EMPTY; + private final Set>> extraClasses = new HashSet<>(); + + public

> ConnectionProtocol.PacketSet addPacket(Class

p_178331_, Function p_178332_) { + int i = this.idToDeserializer.size(); ++ int k = this.contextualIdToDeserializer.size(); ++ if (i != k) { ++ throw new IllegalStateException("Deserializer lists must be equal in length! Somebody externally modified the registration!"); ++ } ++ + int j = this.classToId.put(p_178331_, i); + if (j != -1) { + String s = "Packet " + p_178331_ + " is already registered to ID " + j; +@@ -552,11 +_,33 @@ + throw new IllegalArgumentException(s); + } else { + this.idToDeserializer.add(p_178332_); ++ this.contextualIdToDeserializer.add((p_296217_, p_130535_) -> p_178332_.apply(p_296217_)); //NeoForge: We always want to be able to create a packet from a buffer, even if we don't have a channel context ++ return this; ++ } ++ } ++ ++ public

> ConnectionProtocol.PacketSet addContextualPacket(Class

p_178331_, java.util.function.BiFunction readerBuilder) { ++ int i = this.contextualIdToDeserializer.size(); ++ int k = this.idToDeserializer.size(); ++ if (i != k) { ++ throw new IllegalStateException("Deserializer lists must be equal in length! Somebody externally modified the registration!"); ++ } ++ ++ int j = this.classToId.put(p_178331_, i); ++ if (j != -1) { ++ String s = "Packet " + p_178331_ + " is already registered to ID " + j; ++ LOGGER.error(LogUtils.FATAL_MARKER, s); ++ throw new IllegalArgumentException(s); ++ } else { ++ this.idToDeserializer.add((buffer -> { ++ throw new IllegalStateException("Cannot deserialize contextual packet: " + p_178331_.getSimpleName() + " without context"); ++ })); ++ this.contextualIdToDeserializer.add(readerBuilder); + return this; + } + } + +- public

> ConnectionProtocol.PacketSet withBundlePacket(Class

p_265034_, Function>, P> p_265591_) { ++ public

> ConnectionProtocol.PacketSet withBundlePacket(Class

p_265034_, Function>, P> p_265591_) { + if (this.bundlerInfo != BundlerInfo.EMPTY) { + throw new IllegalStateException("Bundle packet already configured"); + } else { +@@ -575,11 +_,29 @@ + public boolean isKnownPacket(Class p_295070_) { + return this.classToId.containsKey(p_295070_) || this.extraClasses.contains(p_295070_); + } +- ++ ++ /** ++ * @deprecated Use {@link #createPacket(int, FriendlyByteBuf, io.netty.channel.ChannelHandlerContext)} instead, which provides the channel context for creating custom packet payloads. ++ */ ++ @Deprecated + @Nullable + public Packet createPacket(int p_178328_, FriendlyByteBuf p_178329_) { + Function> function = this.idToDeserializer.get(p_178328_); + return function != null ? function.apply(p_178329_) : null; ++ } ++ ++ /** ++ * Creates a new packet from the given discriminator and buffer. ++ * ++ * @param p_178328_ The discriminator ++ * @param p_178329_ The buffer ++ * @param p_130535_ The channel context ++ * @return The packet, or null if no packet could be read. ++ */ ++ @Nullable ++ public Packet createPacket(int p_178328_, FriendlyByteBuf p_178329_, io.netty.channel.ChannelHandlerContext p_130535_) { ++ java.util.function.BiFunction> function = this.contextualIdToDeserializer.get(p_178328_); ++ return function != null ? function.apply(p_178329_, p_130535_) : null; + } + + public BundlerInfo bundlerInfo() { diff --git a/patches/net/minecraft/network/PacketBundleUnpacker.java.patch b/patches/net/minecraft/network/PacketBundleUnpacker.java.patch new file mode 100644 index 0000000000..1c153d15a8 --- /dev/null +++ b/patches/net/minecraft/network/PacketBundleUnpacker.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/PacketBundleUnpacker.java ++++ b/net/minecraft/network/PacketBundleUnpacker.java +@@ -20,7 +_,7 @@ + if (bundlerinfo$provider == null) { + throw new EncoderException("Bundler not configured: " + p_265038_); + } else { +- bundlerinfo$provider.bundlerInfo().unbundlePacket(p_265038_, p_265735_::add); ++ bundlerinfo$provider.bundlerInfo().unbundlePacket(p_265038_, p_265735_::add, p_265691_); + } + } + } diff --git a/patches/net/minecraft/network/PacketDecoder.java.patch b/patches/net/minecraft/network/PacketDecoder.java.patch new file mode 100644 index 0000000000..fd0a0ecc06 --- /dev/null +++ b/patches/net/minecraft/network/PacketDecoder.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/PacketDecoder.java ++++ b/net/minecraft/network/PacketDecoder.java +@@ -28,7 +_,7 @@ + ConnectionProtocol.CodecData codecdata = attribute.get(); + FriendlyByteBuf friendlybytebuf = new FriendlyByteBuf(p_130536_); + int j = friendlybytebuf.readVarInt(); +- Packet packet = codecdata.createPacket(j, friendlybytebuf); ++ Packet packet = codecdata.createPacket(j, friendlybytebuf, p_130535_); + if (packet == null) { + throw new IOException("Bad packet id " + j); + } else { diff --git a/patches/net/minecraft/network/protocol/BundlePacket.java.patch b/patches/net/minecraft/network/protocol/BundlePacket.java.patch new file mode 100644 index 0000000000..e14526cf49 --- /dev/null +++ b/patches/net/minecraft/network/protocol/BundlePacket.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/network/protocol/BundlePacket.java ++++ b/net/minecraft/network/protocol/BundlePacket.java +@@ -4,13 +_,13 @@ + import net.minecraft.network.PacketListener; + + public abstract class BundlePacket implements Packet { +- private final Iterable> packets; ++ private final Iterable> packets; + +- protected BundlePacket(Iterable> p_265290_) { +- this.packets = p_265290_; ++ protected BundlePacket(Iterable> p_265290_) { ++ this.packets = net.neoforged.neoforge.network.bundle.BundlePacketUtils.flatten(p_265290_); + } + +- public final Iterable> subPackets() { ++ public final Iterable> subPackets() { + return this.packets; + } + diff --git a/patches/net/minecraft/network/protocol/BundlerInfo.java.patch b/patches/net/minecraft/network/protocol/BundlerInfo.java.patch new file mode 100644 index 0000000000..571d6553f0 --- /dev/null +++ b/patches/net/minecraft/network/protocol/BundlerInfo.java.patch @@ -0,0 +1,69 @@ +--- a/net/minecraft/network/protocol/BundlerInfo.java ++++ b/net/minecraft/network/protocol/BundlerInfo.java +@@ -23,7 +_,7 @@ + }; + + static > BundlerInfo createForPacket( +- final Class

p_265438_, final Function>, P> p_265627_, final BundleDelimiterPacket p_265373_ ++ final Class

p_265438_, final Function>, P> p_265627_, final BundleDelimiterPacket p_265373_ + ) { + return new BundlerInfo() { + @Override +@@ -38,11 +_,31 @@ + } + } + ++ @Override ++ public void unbundlePacket(Packet bundlePacket, Consumer> packetSender, io.netty.channel.ChannelHandlerContext context) { ++ if (bundlePacket.getClass() == p_265438_) { ++ P p = (P)bundlePacket; ++ java.util.List> packets = net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().filterGameBundlePackets(context, p.subPackets()); ++ if (packets.isEmpty()) { ++ return; ++ } ++ if (packets.size() == 1) { ++ packetSender.accept(packets.get(0)); ++ return; ++ } ++ packetSender.accept(p_265373_); ++ packets.forEach(packetSender); ++ packetSender.accept(p_265373_); ++ } else { ++ packetSender.accept(bundlePacket); ++ } ++ } ++ + @Nullable + @Override + public BundlerInfo.Bundler startPacketBundling(Packet p_265097_) { + return p_265097_ == p_265373_ ? new BundlerInfo.Bundler() { +- private final List> bundlePackets = new ArrayList<>(); ++ private final List> bundlePackets = new ArrayList<>(); + + @Nullable + @Override +@@ -61,7 +_,24 @@ + }; + } + ++ /** ++ * @deprecated Use {@link #unbundlePacket(Packet, Consumer, io.netty.channel.ChannelHandlerContext)} instead, as it supports packet filtering and is more efficient. ++ */ ++ @Deprecated + void unbundlePacket(Packet p_265095_, Consumer> p_265715_); ++ ++ /** ++ * Unwrap and flattens a bundle packet. ++ * Then sends the packets contained in the bundle, bracketing them in delimiter packets if need be. ++ * ++ * @param bundlePacket The bundle packet to write. ++ * @param packetSender The packet sender. ++ * @param context The network context. ++ * @implNote This implementation should filter out packets which are not sendable on the current context, however to preserve compatibility the default implementation does not do this. ++ */ ++ default void unbundlePacket(Packet bundlePacket, Consumer> packetSender, io.netty.channel.ChannelHandlerContext context) { ++ unbundlePacket(bundlePacket, packetSender); ++ } + + @Nullable + BundlerInfo.Bundler startPacketBundling(Packet p_265162_); diff --git a/patches/net/minecraft/network/protocol/PacketFlow.java.patch b/patches/net/minecraft/network/protocol/PacketFlow.java.patch new file mode 100644 index 0000000000..5daa652952 --- /dev/null +++ b/patches/net/minecraft/network/protocol/PacketFlow.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/network/protocol/PacketFlow.java ++++ b/net/minecraft/network/protocol/PacketFlow.java +@@ -1,6 +_,6 @@ + package net.minecraft.network.protocol; + +-public enum PacketFlow { ++public enum PacketFlow implements net.neoforged.neoforge.common.extensions.IPacketFlowExtension { + SERVERBOUND, + CLIENTBOUND; + diff --git a/patches/net/minecraft/network/protocol/common/ClientCommonPacketListener.java.patch b/patches/net/minecraft/network/protocol/common/ClientCommonPacketListener.java.patch new file mode 100644 index 0000000000..f63e14ba20 --- /dev/null +++ b/patches/net/minecraft/network/protocol/common/ClientCommonPacketListener.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/common/ClientCommonPacketListener.java ++++ b/net/minecraft/network/protocol/common/ClientCommonPacketListener.java +@@ -2,7 +_,7 @@ + + import net.minecraft.network.ClientboundPacketListener; + +-public interface ClientCommonPacketListener extends ClientboundPacketListener { ++public interface ClientCommonPacketListener extends ClientboundPacketListener, net.neoforged.neoforge.common.extensions.IClientCommonPacketListenerExtension { + void handleKeepAlive(ClientboundKeepAlivePacket p_295236_); + + void handlePing(ClientboundPingPacket p_296451_); diff --git a/patches/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch b/patches/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch index e9197cc4c7..c7be664e92 100644 --- a/patches/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch +++ b/patches/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch @@ -1,21 +1,46 @@ --- a/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java +++ b/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java -@@ -59,14 +_,13 @@ +@@ -53,10 +_,43 @@ + .put(WorldGenAttemptDebugPayload.ID, WorldGenAttemptDebugPayload::new) + .build(); - private static CustomPacketPayload readPayload(ResourceLocation p_294802_, FriendlyByteBuf p_294886_) { - FriendlyByteBuf.Reader reader = KNOWN_TYPES.get(p_294802_); -- return (CustomPacketPayload)(reader != null ? reader.apply(p_294886_) : readUnknownPayload(p_294802_, p_294886_)); -+ return reader != null ? reader.apply(p_294886_) : readUnknownPayload(p_294802_, p_294886_); ++ /** ++ * @deprecated Use {@link #ClientboundCustomPayloadPacket(FriendlyByteBuf, io.netty.channel.ChannelHandlerContext, net.minecraft.network.ConnectionProtocol)} instead, as this variant can only read vanilla payloads. ++ */ ++ @Deprecated() + public ClientboundCustomPayloadPacket(FriendlyByteBuf p_295835_) { + this(readPayload(p_295835_.readResourceLocation(), p_295835_)); } -- private static DiscardedPayload readUnknownPayload(ResourceLocation p_295991_, FriendlyByteBuf p_295803_) { -+ private static net.neoforged.neoforge.network.custom.payload.SimplePayload readUnknownPayload(ResourceLocation p_295991_, FriendlyByteBuf p_295803_) { - int i = p_295803_.readableBytes(); -- if (i >= 0 && i <= 1048576) { -- p_295803_.skipBytes(i); -- return new DiscardedPayload(p_295991_); -+ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) { -+ return net.neoforged.neoforge.network.custom.payload.SimplePayload.inbound(p_295803_, p_295991_); - } else { - throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); - } ++ /** ++ * Reads a custom payload packet from the given buffer. ++ * ++ * @param buffer The buffer to read from ++ * @param context The context of the channel in which this packet is read ++ * @param protocol The protocol of the connection ++ */ ++ public ClientboundCustomPayloadPacket(FriendlyByteBuf buffer, io.netty.channel.ChannelHandlerContext context, net.minecraft.network.ConnectionProtocol protocol) { ++ this(readPayload(buffer.readResourceLocation(), buffer, context, protocol)); ++ } ++ ++ /** ++ * Reads the payload from the given buffer. ++ * ++ * @param p_294367_ The id of the payload ++ * @param p_294321_ The buffer to read from ++ * @param context The context of the channel in which this packet is read ++ * @param protocol The protocol of the connection ++ * @return The payload ++ */ ++ private static CustomPacketPayload readPayload(ResourceLocation p_294367_, FriendlyByteBuf p_294321_, io.netty.channel.ChannelHandlerContext context, net.minecraft.network.ConnectionProtocol protocol) { ++ FriendlyByteBuf.Reader reader = net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().getReader(p_294367_, context, protocol, KNOWN_TYPES); ++ return (CustomPacketPayload)(reader != null ? reader.apply(p_294321_) : readUnknownPayload(p_294367_, p_294321_)); ++ } ++ ++ /** ++ * @deprecated Use {@link #readPayload(ResourceLocation, FriendlyByteBuf, io.netty.channel.ChannelHandlerContext, net.minecraft.network.ConnectionProtocol)} instead, as this variant can only read vanilla payloads. ++ */ ++ @Deprecated + private static CustomPacketPayload readPayload(ResourceLocation p_294802_, FriendlyByteBuf p_294886_) { + FriendlyByteBuf.Reader reader = KNOWN_TYPES.get(p_294802_); + return (CustomPacketPayload)(reader != null ? reader.apply(p_294886_) : readUnknownPayload(p_294802_, p_294886_)); diff --git a/patches/net/minecraft/network/protocol/common/ServerCommonPacketListener.java.patch b/patches/net/minecraft/network/protocol/common/ServerCommonPacketListener.java.patch new file mode 100644 index 0000000000..535b26b1c8 --- /dev/null +++ b/patches/net/minecraft/network/protocol/common/ServerCommonPacketListener.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/common/ServerCommonPacketListener.java ++++ b/net/minecraft/network/protocol/common/ServerCommonPacketListener.java +@@ -2,7 +_,7 @@ + + import net.minecraft.network.protocol.game.ServerPacketListener; + +-public interface ServerCommonPacketListener extends ServerPacketListener { ++public interface ServerCommonPacketListener extends ServerPacketListener, net.neoforged.neoforge.common.extensions.IServerCommonPacketListenerExtension { + void handleKeepAlive(ServerboundKeepAlivePacket p_296457_); + + void handlePong(ServerboundPongPacket p_294309_); diff --git a/patches/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/patches/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch index 1b7d7edf9f..18ad269406 100644 --- a/patches/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch +++ b/patches/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch @@ -1,21 +1,52 @@ --- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java +++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java -@@ -23,14 +_,13 @@ +@@ -17,10 +_,49 @@ + .put(BrandPayload.ID, BrandPayload::new) + .build(); - private static CustomPacketPayload readPayload(ResourceLocation p_294367_, FriendlyByteBuf p_294321_) { - FriendlyByteBuf.Reader reader = KNOWN_TYPES.get(p_294367_); -- return (CustomPacketPayload)(reader != null ? reader.apply(p_294321_) : readUnknownPayload(p_294367_, p_294321_)); -+ return reader != null ? reader.apply(p_294321_) : readUnknownPayload(p_294367_, p_294321_); ++ /** ++ * Creates a new packet with a custom payload from the network. ++ * @param p_296108_ The buffer to read the packet from. ++ * @deprecated Use {@link #ServerboundCustomPayloadPacket(FriendlyByteBuf, io.netty.channel.ChannelHandlerContext, net.minecraft.network.ConnectionProtocol)} instead, as this variant can only read vanilla payloads. ++ */ ++ @Deprecated + public ServerboundCustomPayloadPacket(FriendlyByteBuf p_296108_) { + this(readPayload(p_296108_.readResourceLocation(), p_296108_)); } -- private static DiscardedPayload readUnknownPayload(ResourceLocation p_294973_, FriendlyByteBuf p_296037_) { -+ private static net.neoforged.neoforge.network.custom.payload.SimplePayload readUnknownPayload(ResourceLocation p_294973_, FriendlyByteBuf p_296037_) { - int i = p_296037_.readableBytes(); -- if (i >= 0 && i <= 32767) { -- p_296037_.skipBytes(i); -- return new DiscardedPayload(p_294973_); -+ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) { -+ return net.neoforged.neoforge.network.custom.payload.SimplePayload.inbound(p_296037_, p_294973_); - } else { - throw new IllegalArgumentException("Payload may not be larger than 32767 bytes"); - } ++ /** ++ * Creates a new packet with a custom payload from the network. ++ * ++ * @param p_296108_ The buffer to read the packet from. ++ * @param context The context of the channel handler ++ * @param protocol The protocol of the connection ++ */ ++ public ServerboundCustomPayloadPacket(FriendlyByteBuf p_296108_, io.netty.channel.ChannelHandlerContext context, net.minecraft.network.ConnectionProtocol protocol) { ++ this(readPayload(p_296108_.readResourceLocation(), p_296108_, context, protocol)); ++ } ++ ++ /** ++ * Reads the payload from the given buffer. ++ * ++ * @param p_294367_ The id of the payload ++ * @param p_294321_ The buffer to read from ++ * @param context The context of the channel handler ++ * @param protocol The protocol of the connection ++ * @return The payload ++ */ ++ private static CustomPacketPayload readPayload(ResourceLocation p_294367_, FriendlyByteBuf p_294321_, io.netty.channel.ChannelHandlerContext context, net.minecraft.network.ConnectionProtocol protocol) { ++ FriendlyByteBuf.Reader reader = net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().getReader(p_294367_, context, protocol, KNOWN_TYPES); ++ return (CustomPacketPayload)(reader != null ? reader.apply(p_294321_) : readUnknownPayload(p_294367_, p_294321_)); ++ } ++ ++ /** ++ * Reads the payload from the given buffer. ++ * @param p_294367_ The id of the payload ++ * @param p_294321_ The buffer to read from ++ * @return The payload ++ * @deprecated Use {@link #readPayload(ResourceLocation, FriendlyByteBuf, io.netty.channel.ChannelHandlerContext, net.minecraft.network.ConnectionProtocol)} instead, as this variant can only read vanilla payloads. ++ */ ++ @Deprecated + private static CustomPacketPayload readPayload(ResourceLocation p_294367_, FriendlyByteBuf p_294321_) { + FriendlyByteBuf.Reader reader = KNOWN_TYPES.get(p_294367_); + return (CustomPacketPayload)(reader != null ? reader.apply(p_294321_) : readUnknownPayload(p_294367_, p_294321_)); diff --git a/patches/net/minecraft/network/protocol/configuration/ServerConfigurationPacketListener.java.patch b/patches/net/minecraft/network/protocol/configuration/ServerConfigurationPacketListener.java.patch new file mode 100644 index 0000000000..6badab3f24 --- /dev/null +++ b/patches/net/minecraft/network/protocol/configuration/ServerConfigurationPacketListener.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/configuration/ServerConfigurationPacketListener.java ++++ b/net/minecraft/network/protocol/configuration/ServerConfigurationPacketListener.java +@@ -3,7 +_,7 @@ + import net.minecraft.network.ConnectionProtocol; + import net.minecraft.network.protocol.common.ServerCommonPacketListener; + +-public interface ServerConfigurationPacketListener extends ServerCommonPacketListener { ++public interface ServerConfigurationPacketListener extends ServerCommonPacketListener, net.neoforged.neoforge.common.extensions.IServerConfigurationPacketListenerExtension { + @Override + default ConnectionProtocol protocol() { + return ConnectionProtocol.CONFIGURATION; diff --git a/patches/net/minecraft/network/protocol/game/ClientboundBundlePacket.java.patch b/patches/net/minecraft/network/protocol/game/ClientboundBundlePacket.java.patch new file mode 100644 index 0000000000..076aea75d6 --- /dev/null +++ b/patches/net/minecraft/network/protocol/game/ClientboundBundlePacket.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/game/ClientboundBundlePacket.java ++++ b/net/minecraft/network/protocol/game/ClientboundBundlePacket.java +@@ -4,7 +_,7 @@ + import net.minecraft.network.protocol.Packet; + + public class ClientboundBundlePacket extends BundlePacket { +- public ClientboundBundlePacket(Iterable> p_265231_) { ++ public ClientboundBundlePacket(Iterable> p_265231_) { + super(p_265231_); + } + diff --git a/patches/net/minecraft/network/protocol/game/ServerGamePacketListener.java.patch b/patches/net/minecraft/network/protocol/game/ServerGamePacketListener.java.patch new file mode 100644 index 0000000000..294239c1f5 --- /dev/null +++ b/patches/net/minecraft/network/protocol/game/ServerGamePacketListener.java.patch @@ -0,0 +1,11 @@ +--- a/net/minecraft/network/protocol/game/ServerGamePacketListener.java ++++ b/net/minecraft/network/protocol/game/ServerGamePacketListener.java +@@ -3,7 +_,7 @@ + import net.minecraft.network.ConnectionProtocol; + import net.minecraft.network.protocol.common.ServerCommonPacketListener; + +-public interface ServerGamePacketListener extends ServerPingPacketListener, ServerCommonPacketListener { ++public interface ServerGamePacketListener extends ServerPingPacketListener, ServerCommonPacketListener, net.neoforged.neoforge.common.extensions.IServerGamePacketListenerExtension { + @Override + default ConnectionProtocol protocol() { + return ConnectionProtocol.PLAY; diff --git a/patches/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch b/patches/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch deleted file mode 100644 index 32fba32c09..0000000000 --- a/patches/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java.patch +++ /dev/null @@ -1,49 +0,0 @@ ---- a/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java -+++ b/net/minecraft/network/protocol/handshake/ClientIntentionPacket.java -@@ -4,25 +_,29 @@ - import net.minecraft.network.FriendlyByteBuf; - import net.minecraft.network.protocol.Packet; - --public record ClientIntentionPacket(int protocolVersion, String hostName, int port, ClientIntent intention) implements Packet { -+public record ClientIntentionPacket(int protocolVersion, String hostName, int port, ClientIntent intention, String fmlVersion) implements Packet { - private static final int MAX_HOST_LENGTH = 255; - -+ public ClientIntentionPacket { -+ if (fmlVersion == null) { -+ fmlVersion = net.neoforged.neoforge.network.NetworkHooks.getFMLVersion(hostName); -+ hostName = hostName.split("\0")[0]; -+ } -+ } -+ - @Deprecated - public ClientIntentionPacket(int protocolVersion, String hostName, int port, ClientIntent intention) { -- this.protocolVersion = protocolVersion; -- this.hostName = hostName; -- this.port = port; -- this.intention = intention; -+ this(protocolVersion, hostName, port, intention, net.neoforged.neoforge.network.NetworkConstants.NETVERSION); - } - - public ClientIntentionPacket(FriendlyByteBuf p_179801_) { -- this(p_179801_.readVarInt(), p_179801_.readUtf(255), p_179801_.readUnsignedShort(), ClientIntent.byId(p_179801_.readVarInt())); -+ this(p_179801_.readVarInt(), p_179801_.readUtf(255), p_179801_.readUnsignedShort(), ClientIntent.byId(p_179801_.readVarInt()), null); - } - - @Override - public void write(FriendlyByteBuf p_134737_) { - p_134737_.writeVarInt(this.protocolVersion); -- p_134737_.writeUtf(this.hostName); -+ p_134737_.writeUtf(this.hostName + "\0" + this.fmlVersion); - p_134737_.writeShort(this.port); - p_134737_.writeVarInt(this.intention.id()); - } -@@ -34,5 +_,9 @@ - @Override - public ConnectionProtocol nextProtocol() { - return this.intention.protocol(); -+ } -+ -+ public String getFMLVersion() { -+ return this.fmlVersion; - } - } diff --git a/patches/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch b/patches/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch deleted file mode 100644 index 2753c50240..0000000000 --- a/patches/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch +++ /dev/null @@ -1,17 +0,0 @@ ---- a/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java -+++ b/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java -@@ -17,11 +_,10 @@ - return readUnknownPayload(p_295267_, p_295117_); - } - -- private static DiscardedQueryPayload readUnknownPayload(ResourceLocation p_294837_, FriendlyByteBuf p_296380_) { -+ private static net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload readUnknownPayload(ResourceLocation p_294837_, FriendlyByteBuf p_296380_) { - int i = p_296380_.readableBytes(); -- if (i >= 0 && i <= 1048576) { -- p_296380_.skipBytes(i); -- return new DiscardedQueryPayload(p_294837_); -+ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) { -+ return net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload.inbound(p_296380_, p_294837_); - } else { - throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); - } diff --git a/patches/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch b/patches/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch deleted file mode 100644 index 0aedcd7aca..0000000000 --- a/patches/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch +++ /dev/null @@ -1,22 +0,0 @@ ---- a/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java -+++ b/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java -@@ -14,15 +_,14 @@ - return new ServerboundCustomQueryAnswerPacket(i, readPayload(i, p_295711_)); - } - -- private static CustomQueryAnswerPayload readPayload(int p_296215_, FriendlyByteBuf p_295168_) { -+ private static net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload readPayload(int p_296215_, FriendlyByteBuf p_295168_) { - return readUnknownPayload(p_295168_); - } - -- private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf p_294928_) { -+ private static net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload readUnknownPayload(FriendlyByteBuf p_294928_) { - int i = p_294928_.readableBytes(); -- if (i >= 0 && i <= 1048576) { -- p_294928_.skipBytes(i); -- return DiscardedQueryAnswerPayload.INSTANCE; -+ if (i >= 0 && i <= MAX_PAYLOAD_SIZE) { -+ return p_294928_.readNullable(net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload::inbound); - } else { - throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); - } diff --git a/patches/net/minecraft/network/protocol/status/ServerStatus.java.patch b/patches/net/minecraft/network/protocol/status/ServerStatus.java.patch index 0802126f37..e689a1900e 100644 --- a/patches/net/minecraft/network/protocol/status/ServerStatus.java.patch +++ b/patches/net/minecraft/network/protocol/status/ServerStatus.java.patch @@ -6,7 +6,7 @@ Optional favicon, - boolean enforcesSecureChat + boolean enforcesSecureChat, -+ Optional neoForgeData ++ boolean isModded ) { public static final Codec CODEC = RecordCodecBuilder.create( p_304170_ -> p_304170_.group( @@ -16,7 +16,7 @@ ServerStatus.Favicon.CODEC.optionalFieldOf("favicon").forGetter(ServerStatus::favicon), - Codec.BOOL.optionalFieldOf("enforcesSecureChat", Boolean.valueOf(false)).forGetter(ServerStatus::enforcesSecureChat) + Codec.BOOL.optionalFieldOf("enforcesSecureChat", Boolean.valueOf(false)).forGetter(ServerStatus::enforcesSecureChat), -+ net.neoforged.neoforge.network.ServerStatusPing.CODEC.optionalFieldOf("neoForgeData").forGetter(ServerStatus::neoForgeData) ++ Codec.BOOL.optionalFieldOf("isModded", Boolean.FALSE).forGetter(ServerStatus::isModded) ) .apply(p_304170_, ServerStatus::new) ); diff --git a/patches/net/minecraft/server/MinecraftServer.java.patch b/patches/net/minecraft/server/MinecraftServer.java.patch index 56b707d4eb..f977b10245 100644 --- a/patches/net/minecraft/server/MinecraftServer.java.patch +++ b/patches/net/minecraft/server/MinecraftServer.java.patch @@ -125,7 +125,7 @@ Optional.ofNullable(this.statusIcon), - this.enforceSecureProfile() + this.enforceSecureProfile(), -+ Optional.of(new net.neoforged.neoforge.network.ServerStatusPing()) ++ true //TODO Neo: Possible build a system which indicates what the status of the modded server is. ); } diff --git a/patches/net/minecraft/server/level/ServerChunkCache.java.patch b/patches/net/minecraft/server/level/ServerChunkCache.java.patch index c45111fe87..736014f095 100644 --- a/patches/net/minecraft/server/level/ServerChunkCache.java.patch +++ b/patches/net/minecraft/server/level/ServerChunkCache.java.patch @@ -1,5 +1,14 @@ --- a/net/minecraft/server/level/ServerChunkCache.java +++ b/net/minecraft/server/level/ServerChunkCache.java +@@ -45,7 +_,7 @@ + import net.minecraft.world.level.storage.DimensionDataStorage; + import net.minecraft.world.level.storage.LevelStorageSource; + +-public class ServerChunkCache extends ChunkSource { ++public class ServerChunkCache extends ChunkSource implements net.neoforged.neoforge.common.extensions.IServerChunkCacheExtension { + private static final List CHUNK_STATUSES = ChunkStatus.getStatusList(); + private final DistanceManager distanceManager; + public final ServerLevel level; @@ -150,6 +_,10 @@ } } diff --git a/patches/net/minecraft/server/level/ServerEntity.java.patch b/patches/net/minecraft/server/level/ServerEntity.java.patch index dcdb8f2cf7..7936973d00 100644 --- a/patches/net/minecraft/server/level/ServerEntity.java.patch +++ b/patches/net/minecraft/server/level/ServerEntity.java.patch @@ -12,7 +12,7 @@ if (mapitemsaveddata != null) { for(ServerPlayer serverplayer : this.level.players()) { mapitemsaveddata.tickCarriedBy(serverplayer, itemstack); -@@ -219,6 +_,7 @@ +@@ -219,16 +_,18 @@ public void removePairing(ServerPlayer p_8535_) { this.entity.stopSeenByPlayer(p_8535_); p_8535_.connection.send(new ClientboundRemoveEntitiesPacket(this.entity.getId())); @@ -20,11 +20,25 @@ } public void addPairing(ServerPlayer p_8542_) { -@@ -226,6 +_,7 @@ - this.sendPairingData(p_8542_, list::add); +- List> list = new ArrayList<>(); +- this.sendPairingData(p_8542_, list::add); ++ List> list = new ArrayList<>(); ++ this.sendPairingData(p_8542_, new net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor<>(list::add)); p_8542_.connection.send(new ClientboundBundlePacket(list)); this.entity.startSeenByPlayer(p_8542_); + net.neoforged.neoforge.event.EventHooks.onStartEntityTracking(this.entity, p_8542_); } - public void sendPairingData(ServerPlayer p_289562_, Consumer> p_289563_) { +- public void sendPairingData(ServerPlayer p_289562_, Consumer> p_289563_) { ++ public void sendPairingData(ServerPlayer p_289562_, net.neoforged.neoforge.network.bundle.PacketAndPayloadAcceptor p_289563_) { + if (this.entity.isRemoved()) { + LOGGER.warn("Fetching packet for removed entity {}", this.entity); + } +@@ -236,6 +_,7 @@ + Packet packet = this.entity.getAddEntityPacket(); + this.yHeadRotp = Mth.floor(this.entity.getYHeadRot() * 256.0F / 360.0F); + p_289563_.accept(packet); ++ this.entity.sendPairingData(p_289562_, p_289563_::accept); + if (this.trackedDataValues != null) { + p_289563_.accept(new ClientboundSetEntityDataPacket(this.entity.getId(), this.trackedDataValues)); + } diff --git a/patches/net/minecraft/server/level/ServerPlayer.java.patch b/patches/net/minecraft/server/level/ServerPlayer.java.patch index a8a70b3b77..369728c2af 100644 --- a/patches/net/minecraft/server/level/ServerPlayer.java.patch +++ b/patches/net/minecraft/server/level/ServerPlayer.java.patch @@ -91,8 +91,37 @@ return this.isReachableBedBlock(p_9117_) || this.isReachableBedBlock(p_9117_.relative(p_9118_.getOpposite())); } -@@ -1003,6 +_,7 @@ +@@ -983,11 +_,19 @@ + + @Override + public OptionalInt openMenu(@Nullable MenuProvider p_9033_) { ++ return openMenu(p_9033_, (java.util.function.Consumer) null); ++ } ++ ++ @Override ++ public OptionalInt openMenu(@Nullable MenuProvider p_9033_, @Nullable java.util.function.Consumer extraDataWriter) { + if (p_9033_ == null) { + return OptionalInt.empty(); + } else { + if (this.containerMenu != this.inventoryMenu) { ++ if (p_9033_.shouldTriggerClientSideContainerClosingOnOpen()) + this.closeContainer(); ++ else ++ this.doCloseContainer(); + } + + this.nextContainerCounter(); +@@ -999,10 +_,16 @@ + + return OptionalInt.empty(); + } else { ++ if (extraDataWriter == null) { + this.connection .send(new ClientboundOpenScreenPacket(abstractcontainermenu.containerId, abstractcontainermenu.getType(), p_9033_.getDisplayName())); ++ } else { ++ this.connection ++ .send(new net.neoforged.neoforge.network.payload.AdvancedOpenScreenPayload(abstractcontainermenu.containerId, abstractcontainermenu.getType(), p_9033_.getDisplayName(), extraDataWriter)); ++ } this.initMenu(abstractcontainermenu); this.containerMenu = abstractcontainermenu; + net.neoforged.neoforge.common.NeoForge.EVENT_BUS.post(new net.neoforged.neoforge.event.entity.player.PlayerContainerEvent.Open(this, this.containerMenu)); diff --git a/patches/net/minecraft/server/network/CommonListenerCookie.java.patch b/patches/net/minecraft/server/network/CommonListenerCookie.java.patch new file mode 100644 index 0000000000..de7a86e1c0 --- /dev/null +++ b/patches/net/minecraft/server/network/CommonListenerCookie.java.patch @@ -0,0 +1,20 @@ +--- a/net/minecraft/server/network/CommonListenerCookie.java ++++ b/net/minecraft/server/network/CommonListenerCookie.java +@@ -3,7 +_,16 @@ + import com.mojang.authlib.GameProfile; + import net.minecraft.server.level.ClientInformation; + +-public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation) { ++public record CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation, boolean isModdedConnection) { ++ ++ /** ++ * @deprecated Use {@link #CommonListenerCookie(GameProfile, int, ClientInformation, boolean)} instead, to indicate whether the connection is modded. ++ */ ++ @Deprecated ++ public CommonListenerCookie(GameProfile gameProfile, int latency, ClientInformation clientInformation) { ++ this(gameProfile, latency, clientInformation, false); ++ } ++ + public static CommonListenerCookie createInitial(GameProfile p_302024_) { + return new CommonListenerCookie(p_302024_, 0, ClientInformation.createDefault()); + } diff --git a/patches/net/minecraft/server/network/ConfigurationTask.java.patch b/patches/net/minecraft/server/network/ConfigurationTask.java.patch new file mode 100644 index 0000000000..7a56379b93 --- /dev/null +++ b/patches/net/minecraft/server/network/ConfigurationTask.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/server/network/ConfigurationTask.java ++++ b/net/minecraft/server/network/ConfigurationTask.java +@@ -9,6 +_,10 @@ + ConfigurationTask.Type type(); + + public static record Type(String id) { ++ public Type(net.minecraft.resources.ResourceLocation location) { ++ this(location.toString()); ++ } ++ + @Override + public String toString() { + return this.id; diff --git a/patches/net/minecraft/server/network/MemoryServerHandshakePacketListenerImpl.java.patch b/patches/net/minecraft/server/network/MemoryServerHandshakePacketListenerImpl.java.patch deleted file mode 100644 index 42f7acda58..0000000000 --- a/patches/net/minecraft/server/network/MemoryServerHandshakePacketListenerImpl.java.patch +++ /dev/null @@ -1,10 +0,0 @@ ---- a/net/minecraft/server/network/MemoryServerHandshakePacketListenerImpl.java -+++ b/net/minecraft/server/network/MemoryServerHandshakePacketListenerImpl.java -@@ -21,6 +_,7 @@ - if (p_9697_.intention() != ClientIntent.LOGIN) { - throw new UnsupportedOperationException("Invalid intention " + p_9697_.intention()); - } else { -+ if (!net.neoforged.neoforge.server.ServerLifecycleHooks.handleServerLogin(p_9697_, this.connection)) return; - this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); - this.connection.setListener(new ServerLoginPacketListenerImpl(this.server, this.connection)); - } diff --git a/patches/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patches/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index fcf24f615a..7d0a797c72 100644 --- a/patches/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/patches/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -1,14 +1,52 @@ --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -@@ -68,6 +_,11 @@ +@@ -109,7 +_,12 @@ + this.send(p_294278_, null); + } - @Override - public void handleCustomPayload(ServerboundCustomPayloadPacket p_294276_) { -+ if (p_294276_.payload() instanceof net.neoforged.neoforge.network.custom.payload.SimplePayload simplePayload && net.neoforged.neoforge.network.NetworkHooks.onCustomPayload(p_294276_, simplePayload, connection)) { ++ @Override + public void send(Packet p_295099_, @Nullable PacketSendListener p_296321_) { ++ if (!net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().canSendPacket(p_295099_, this)) { + return; + } + -+ LOGGER.warn("Unknown custom packet payload: {}", p_294276_.payload().id()); + boolean flag = !this.suspendFlushingOnServerThread || !this.server.isSameThread(); + + try { +@@ -143,7 +_,36 @@ + return this.latency; } - @Override ++ /** ++ * Creates a new cookie for this connection. ++ * ++ * @param p_301973_ The client information. ++ * @return The cookie. ++ * @deprecated Use {@link #createCookie(ClientInformation, boolean)} instead, keeping the connection type information available. ++ */ ++ @Deprecated + protected CommonListenerCookie createCookie(ClientInformation p_301973_) { + return new CommonListenerCookie(this.playerProfile(), this.latency, p_301973_); ++ } ++ ++ /** ++ * Creates a new cookie for this connection. ++ * ++ * @param p_301973_ The client information. ++ * @param isModdedConnection Whether the connection is modded. ++ * @return The cookie. ++ */ ++ protected CommonListenerCookie createCookie(ClientInformation p_301973_, boolean isModdedConnection) { ++ return new CommonListenerCookie(this.playerProfile(), this.latency, p_301973_, isModdedConnection); ++ } ++ ++ @Override ++ public Connection getConnection() { ++ return connection; ++ } ++ ++ @Override ++ public net.minecraft.util.thread.ReentrantBlockableEventLoop getMainThreadEventLoop() { ++ return server; + } + } diff --git a/patches/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/patches/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch new file mode 100644 index 0000000000..409adc0556 --- /dev/null +++ b/patches/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -0,0 +1,96 @@ +--- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java ++++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +@@ -41,6 +_,8 @@ + @Nullable + private ConfigurationTask currentTask; + private ClientInformation clientInformation; ++ private boolean isModdedConnection = false; ++ private boolean isHandlingModdedConfigurationPhase = false; + + public ServerConfigurationPacketListenerImpl(MinecraftServer p_294645_, Connection p_295787_, CommonListenerCookie p_302003_) { + super(p_294645_, p_295787_, p_302003_); +@@ -65,6 +_,11 @@ + } + + public void startConfiguration() { ++ this.send(new net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload()); ++ this.send(new net.minecraft.network.protocol.common.ClientboundPingPacket(0)); ++ } ++ ++ private void runConfiguration() { + this.send(new ClientboundCustomPayloadPacket(new BrandPayload(this.server.getServerModName()))); + LayeredRegistryAccess layeredregistryaccess = this.server.registries(); + this.send(new ClientboundUpdateEnabledFeaturesPacket(FeatureFlags.REGISTRY.toNames(this.server.getWorldData().enabledFeatures()))); +@@ -86,8 +_,45 @@ + + private void addOptionalTasks() { + this.server.getServerResourcePack().ifPresent(p_296496_ -> this.configurationTasks.add(new ServerResourcePackConfigurationTask(p_296496_))); +- } +- ++ ++ this.configurationTasks.add(new net.neoforged.neoforge.network.configuration.ModdedConfigurationPhaseStarted(this)); ++ this.configurationTasks.addAll(net.neoforged.fml.ModLoader.get().postEventWithReturn(new net.neoforged.neoforge.network.event.OnGameConfigurationEvent(this)).getConfigurationTasks()); ++ this.configurationTasks.add(new net.neoforged.neoforge.network.configuration.ModdedConfigurationPhaseCompleted(this)); ++ } ++ ++ @Override ++ public void handleCustomPayload(net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket p_294276_) { ++ if (p_294276_.payload() instanceof net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload moddedEnvironmentPayload) { ++ this.isModdedConnection = true; ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance() ++ .onModdedConnectionDetectedAtServer( ++ this, ++ moddedEnvironmentPayload.configuration(), ++ moddedEnvironmentPayload.play() ++ ); ++ return; ++ } ++ ++ if (!isHandlingModdedConfigurationPhase) { ++ super.handleCustomPayload(p_294276_); ++ return; ++ } ++ ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onModdedPacketAtServer(this, p_294276_); ++ } ++ ++ @Override ++ public void handlePong(net.minecraft.network.protocol.common.ServerboundPongPacket p_295142_) { ++ super.handlePong(p_295142_); ++ if (p_295142_.getId() == 0) { ++ if (!this.isModdedConnection && !net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onVanillaConnectionDetectedAtServer(this)) { ++ return; ++ } ++ ++ this.runConfiguration(); ++ } ++ } ++ + @Override + public void handleClientInformation(ServerboundClientInformationPacket p_302032_) { + this.clientInformation = p_302032_.information(); +@@ -121,7 +_,7 @@ + } + + ServerPlayer serverplayer = playerlist.getPlayerForLogin(this.gameProfile, this.clientInformation); +- playerlist.placeNewPlayer(this.connection, serverplayer, this.createCookie(this.clientInformation)); ++ playerlist.placeNewPlayer(this.connection, serverplayer, this.createCookie(this.clientInformation, this.isModdedConnection)); + this.connection.resumeInboundAfterProtocolChange(); + } catch (Exception exception) { + LOGGER.error("Couldn't place player in world", (Throwable)exception); +@@ -155,5 +_,15 @@ + this.currentTask = null; + this.startNextTask(); + } ++ } ++ ++ public void onModdedConfigurationPhaseStarted() { ++ isHandlingModdedConfigurationPhase = true; ++ finishCurrentTask(net.neoforged.neoforge.network.configuration.ModdedConfigurationPhaseStarted.TYPE); ++ } ++ ++ public void onModdedConfigurationPhaseEnded() { ++ isHandlingModdedConfigurationPhase = false; ++ finishCurrentTask(net.neoforged.neoforge.network.configuration.ModdedConfigurationPhaseCompleted.TYPE); + } + } diff --git a/patches/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patches/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 025564a600..f933f04d69 100644 --- a/patches/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/patches/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -13,6 +13,22 @@ public static final double MAX_INTERACTION_DISTANCE = Mth.square(6.0); private static final int NO_BLOCK_UPDATES_TO_ACK = -1; private static final int TRACKED_MESSAGE_DISCONNECT_THRESHOLD = 4096; +@@ -228,6 +_,7 @@ + private final MessageSignatureCache messageSignatureCache = MessageSignatureCache.createDefault(); + private final FutureChain chatMessageChain; + private boolean waitingForSwitchToConfig; ++ private final boolean isModdedConnection; + + public ServerGamePacketListenerImpl(MinecraftServer p_9770_, Connection p_9771_, ServerPlayer p_9772_, CommonListenerCookie p_301978_) { + super(p_9770_, p_9771_, p_301978_); +@@ -238,6 +_,7 @@ + p_9772_.getTextFilter().join(); + this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(p_9772_.getUUID(), p_9770_::enforceSecureProfile); + this.chatMessageChain = new FutureChain(p_9770_); ++ this.isModdedConnection = p_301978_.isModdedConnection(); + } + + @Override @@ -424,9 +_,11 @@ } @@ -122,3 +138,29 @@ } @Override +@@ -1745,7 +_,7 @@ + throw new IllegalStateException("Client acknowledged config, but none was requested"); + } else { + this.connection +- .setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation()))); ++ .setListener(new ServerConfigurationPacketListenerImpl(this.server, this.connection, this.createCookie(this.player.clientInformation(), this.isModdedConnection))); + } + } + +@@ -1779,5 +_,16 @@ + @FunctionalInterface + interface EntityInteraction { + InteractionResult run(ServerPlayer p_143695_, Entity p_143696_, InteractionHand p_143697_); ++ } ++ ++ @Override ++ public void handleCustomPayload(net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket p_294276_) { ++ if (!this.isModdedConnection) { ++ super.handleCustomPayload(p_294276_); ++ } ++ ++ net.neoforged.neoforge.network.registration.NetworkRegistry.getInstance().onModdedPacketAtServer( ++ this, p_294276_ ++ ); + } + } diff --git a/patches/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch b/patches/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch deleted file mode 100644 index 61e31e8770..0000000000 --- a/patches/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java.patch +++ /dev/null @@ -1,19 +0,0 @@ ---- a/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerHandshakePacketListenerImpl.java -@@ -22,6 +_,7 @@ - - @Override - public void handleIntention(ClientIntentionPacket p_9975_) { -+ if (!net.neoforged.neoforge.server.ServerLifecycleHooks.handleServerLogin(p_9975_, this.connection)) return; - switch(p_9975_.intention()) { - case LOGIN: - this.connection.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); -@@ -43,7 +_,7 @@ - ServerStatus serverstatus = this.server.getStatus(); - if (this.server.repliesToStatus() && serverstatus != null) { - this.connection.setClientboundProtocolAfterHandshake(ClientIntent.STATUS); -- this.connection.setListener(new ServerStatusPacketListenerImpl(serverstatus, this.connection)); -+ this.connection.setListener(new ServerStatusPacketListenerImpl(serverstatus, this.connection, this.server.getStatusJson())); - } else { - this.connection.disconnect(IGNORE_STATUS_REASON); - } diff --git a/patches/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch b/patches/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch deleted file mode 100644 index ec1b3be06c..0000000000 --- a/patches/net/minecraft/server/network/ServerLoginPacketListenerImpl.java.patch +++ /dev/null @@ -1,65 +0,0 @@ ---- a/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -+++ b/net/minecraft/server/network/ServerLoginPacketListenerImpl.java -@@ -64,6 +_,14 @@ - - @Override - public void tick() { -+ if (this.state == State.NEGOTIATING) { -+ // We force the state into "NEGOTIATING" which is otherwise unused. Once we're completed we move the negotiation onto "VERIFYING" -+ boolean negotiationComplete = net.neoforged.neoforge.network.NetworkHooks.tickNegotiation(this.connection); -+ if (negotiationComplete) { -+ this.state = State.VERIFYING; -+ } -+ } -+ - if (this.state == ServerLoginPacketListenerImpl.State.VERIFYING) { - this.verifyLoginAndFinishConnectionSetup(Objects.requireNonNull(this.authenticatedProfile)); - } -@@ -104,7 +_,8 @@ - - public String getUserName() { - String s = this.connection.getLoggableAddress(this.server.logIPs()); -- return this.requestedUsername != null ? this.requestedUsername + " (" + s + ")" : s; -+ final String addressString = net.neoforged.neoforge.network.DualStackUtils.getAddressString(this.connection.getRemoteAddress()); -+ return this.authenticatedProfile != null ? this.authenticatedProfile + " (" + addressString + ")" : addressString; - } - - @Override -@@ -127,7 +_,7 @@ - - void startClientVerification(GameProfile p_295643_) { - this.authenticatedProfile = p_295643_; -- this.state = ServerLoginPacketListenerImpl.State.VERIFYING; -+ this.state = State.NEGOTIATING; //Forge: Before verification, negotiate connection properties: - } - - private void verifyLoginAndFinishConnectionSetup(GameProfile p_294502_) { -@@ -179,7 +_,7 @@ - throw new IllegalStateException("Protocol error", cryptexception); - } - -- Thread thread = new Thread("User Authenticator #" + UNIQUE_THREAD_ID.incrementAndGet()) { -+ Thread thread = new Thread(net.neoforged.fml.util.thread.SidedThreadGroups.SERVER, "User Authenticator #" + UNIQUE_THREAD_ID.incrementAndGet()) { - @Override - public void run() { - String s1 = Objects.requireNonNull(ServerLoginPacketListenerImpl.this.requestedUsername, "Player name not initialized"); -@@ -200,7 +_,7 @@ - } catch (AuthenticationUnavailableException authenticationunavailableexception) { - if (ServerLoginPacketListenerImpl.this.server.isSingleplayer()) { - ServerLoginPacketListenerImpl.LOGGER.warn("Authentication servers are down but will let them in anyway!"); -- ServerLoginPacketListenerImpl.this.startClientVerification(UUIDUtil.createOfflineProfile(s1)); -+ ServerLoginPacketListenerImpl.this.state = ServerLoginPacketListenerImpl.State.NEGOTIATING; // FORGE: continue NEGOTIATING, we move to READY_TO_ACCEPT after Forge is ready - } else { - ServerLoginPacketListenerImpl.this.disconnect(Component.translatable("multiplayer.disconnect.authservers_down")); - ServerLoginPacketListenerImpl.LOGGER.error("Couldn't verify username because servers are unavailable"); -@@ -222,6 +_,10 @@ - - @Override - public void handleCustomQueryPacket(ServerboundCustomQueryAnswerPacket p_295398_) { -+ if (p_295398_.payload() instanceof net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload simpleQueryPayload && net.neoforged.neoforge.network.NetworkHooks.onCustomQuery(p_295398_, simpleQueryPayload, connection)) { -+ return; -+ } -+ - this.disconnect(DISCONNECT_UNEXPECTED_QUERY); - } - diff --git a/patches/net/minecraft/server/players/PlayerList.java.patch b/patches/net/minecraft/server/players/PlayerList.java.patch index 64a50d4fa7..e9f2695017 100644 --- a/patches/net/minecraft/server/players/PlayerList.java.patch +++ b/patches/net/minecraft/server/players/PlayerList.java.patch @@ -8,14 +8,6 @@ public PlayerList(MinecraftServer p_203842_, LayeredRegistryAccess p_251844_, PlayerDataStorage p_203844_, int p_203845_) { this.server = p_203842_; -@@ -173,6 +_,7 @@ - LevelData leveldata = serverlevel1.getLevelData(); - p_11263_.loadGameTypes(compoundtag); - ServerGamePacketListenerImpl servergamepacketlistenerimpl = new ServerGamePacketListenerImpl(this.server, p_11262_, p_11263_, p_301988_); -+ net.neoforged.neoforge.network.NetworkHooks.sendMCRegistryPackets(p_11262_, net.neoforged.neoforge.network.PlayNetworkDirection.PLAY_TO_CLIENT); - GameRules gamerules = serverlevel1.getGameRules(); - boolean flag = gamerules.getBoolean(GameRules.RULE_DO_IMMEDIATE_RESPAWN); - boolean flag1 = gamerules.getBoolean(GameRules.RULE_REDUCEDDEBUGINFO); @@ -194,6 +_,7 @@ servergamepacketlistenerimpl.send(new ClientboundChangeDifficultyPacket(leveldata.getDifficulty(), leveldata.isDifficultyLocked())); servergamepacketlistenerimpl.send(new ClientboundPlayerAbilitiesPacket(p_11263_.getAbilities())); diff --git a/patches/net/minecraft/world/MenuProvider.java.patch b/patches/net/minecraft/world/MenuProvider.java.patch new file mode 100644 index 0000000000..4823532c3a --- /dev/null +++ b/patches/net/minecraft/world/MenuProvider.java.patch @@ -0,0 +1,10 @@ +--- a/net/minecraft/world/MenuProvider.java ++++ b/net/minecraft/world/MenuProvider.java +@@ -3,6 +_,6 @@ + import net.minecraft.network.chat.Component; + import net.minecraft.world.inventory.MenuConstructor; + +-public interface MenuProvider extends MenuConstructor { ++public interface MenuProvider extends MenuConstructor, net.neoforged.neoforge.client.extensions.IMenuProviderExtension { + Component getDisplayName(); + } diff --git a/patches/net/minecraft/world/entity/EntityType.java.patch b/patches/net/minecraft/world/entity/EntityType.java.patch index b547fc6117..a27d13c2dd 100644 --- a/patches/net/minecraft/world/entity/EntityType.java.patch +++ b/patches/net/minecraft/world/entity/EntityType.java.patch @@ -1,22 +1,21 @@ --- a/net/minecraft/world/entity/EntityType.java +++ b/net/minecraft/world/entity/EntityType.java -@@ -620,6 +_,11 @@ +@@ -620,6 +_,10 @@ private final EntityDimensions dimensions; private final FeatureFlagSet requiredFeatures; + private final java.util.function.Predicate> velocityUpdateSupplier; + private final java.util.function.ToIntFunction> trackingRangeSupplier; + private final java.util.function.ToIntFunction> updateIntervalSupplier; -+ private final java.util.function.BiFunction customClientFactory; + private static EntityType register(String p_20635_, EntityType.Builder p_20636_) { return Registry.register(BuiltInRegistries.ENTITY_TYPE, p_20635_, p_20636_.build(p_20635_)); } -@@ -645,6 +_,25 @@ +@@ -645,6 +_,24 @@ int p_273451_, FeatureFlagSet p_273518_ ) { -+ this(p_273268_, p_272918_, p_273417_, p_273389_, p_273556_, p_272654_, p_273631_, p_272946_, p_272895_, p_273451_, p_273518_, EntityType::defaultVelocitySupplier, EntityType::defaultTrackingRangeSupplier, EntityType::defaultUpdateIntervalSupplier, null); ++ this(p_273268_, p_272918_, p_273417_, p_273389_, p_273556_, p_272654_, p_273631_, p_272946_, p_272895_, p_273451_, p_273518_, EntityType::defaultVelocitySupplier, EntityType::defaultTrackingRangeSupplier, EntityType::defaultUpdateIntervalSupplier); + } + public EntityType( + EntityType.EntityFactory p_273268_, @@ -32,20 +31,18 @@ + FeatureFlagSet p_273518_, + final java.util.function.Predicate> velocityUpdateSupplier, + final java.util.function.ToIntFunction> trackingRangeSupplier, -+ final java.util.function.ToIntFunction> updateIntervalSupplier, -+ final java.util.function.BiFunction customClientFactory ++ final java.util.function.ToIntFunction> updateIntervalSupplier + ) { this.factory = p_273268_; this.category = p_272918_; this.canSpawnFarFromPlayer = p_272654_; -@@ -656,6 +_,10 @@ +@@ -656,6 +_,9 @@ this.clientTrackingRange = p_272895_; this.updateInterval = p_273451_; this.requiredFeatures = p_273518_; + this.velocityUpdateSupplier = velocityUpdateSupplier; + this.trackingRangeSupplier = trackingRangeSupplier; + this.updateIntervalSupplier = updateIntervalSupplier; -+ this.customClientFactory = customClientFactory; } @Nullable @@ -76,32 +73,27 @@ return this.updateInterval; } -@@ -1001,6 +_,12 @@ +@@ -1001,6 +_,8 @@ return this.builtInRegistryHolder; } -+ public T customClientSpawn(net.neoforged.neoforge.network.PlayMessages.SpawnEntity packet, Level world) { -+ if (customClientFactory == null) return this.create(world); -+ return customClientFactory.apply(packet, world); -+ } + public Stream>> getTags() {return this.builtInRegistryHolder().tags();} + public static class Builder { private final EntityType.EntityFactory factory; private final MobCategory category; -@@ -1014,6 +_,11 @@ +@@ -1014,6 +_,10 @@ private EntityDimensions dimensions = EntityDimensions.scalable(0.6F, 1.8F); private FeatureFlagSet requiredFeatures = FeatureFlags.VANILLA_SET; + private java.util.function.Predicate> velocityUpdateSupplier = EntityType::defaultVelocitySupplier; + private java.util.function.ToIntFunction> trackingRangeSupplier = EntityType::defaultTrackingRangeSupplier; + private java.util.function.ToIntFunction> updateIntervalSupplier = EntityType::defaultUpdateIntervalSupplier; -+ private java.util.function.BiFunction customClientFactory; + private Builder(EntityType.EntityFactory p_20696_, MobCategory p_20697_) { this.factory = p_20696_; this.category = p_20697_; -@@ -1073,6 +_,30 @@ +@@ -1073,6 +_,21 @@ return this; } @@ -119,20 +111,11 @@ + this.velocityUpdateSupplier = t->value; + return this; + } -+ -+ /** -+ * By default, entities are spawned clientside via {@link EntityType#create(Level)}}. -+ * If you need finer control over the spawning process, use this to get read access to the spawn packet. -+ */ -+ public EntityType.Builder setCustomClientFactory(java.util.function.BiFunction customClientFactory) { -+ this.customClientFactory = customClientFactory; -+ return this; -+ } + public EntityType build(String p_20713_) { if (this.serialize) { Util.fetchChoiceType(References.ENTITY_TREE, p_20713_); -@@ -1089,7 +_,11 @@ +@@ -1089,7 +_,10 @@ this.dimensions, this.clientTrackingRange, this.updateInterval, @@ -140,8 +123,7 @@ + this.requiredFeatures, + velocityUpdateSupplier, + trackingRangeSupplier, -+ updateIntervalSupplier, -+ customClientFactory ++ updateIntervalSupplier ); } } diff --git a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java index a6be845811..93b1a1a752 100644 --- a/src/main/java/net/neoforged/neoforge/client/ClientHooks.java +++ b/src/main/java/net/neoforged/neoforge/client/ClientHooks.java @@ -19,7 +19,6 @@ import java.io.File; import java.io.IOException; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.HashMap; @@ -30,7 +29,6 @@ import java.util.Set; import java.util.Stack; import java.util.UUID; -import java.util.concurrent.atomic.AtomicBoolean; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -52,7 +50,6 @@ import net.minecraft.client.gui.screens.TitleScreen; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipComponent; import net.minecraft.client.gui.screens.inventory.tooltip.ClientTooltipPositioner; -import net.minecraft.client.gui.screens.multiplayer.JoinMultiplayerScreen; import net.minecraft.client.model.HumanoidModel; import net.minecraft.client.model.Model; import net.minecraft.client.model.geom.ModelLayerLocation; @@ -60,7 +57,6 @@ import net.minecraft.client.multiplayer.ClientLevel; import net.minecraft.client.multiplayer.MultiPlayerGameMode; import net.minecraft.client.multiplayer.PlayerInfo; -import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.particle.ParticleEngine; import net.minecraft.client.particle.ParticleRenderType; import net.minecraft.client.player.AbstractClientPlayer; @@ -99,7 +95,6 @@ import net.minecraft.network.chat.FormattedText; import net.minecraft.network.chat.PlayerChatMessage; import net.minecraft.network.chat.Style; -import net.minecraft.network.protocol.status.ServerStatus; import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.packs.resources.ReloadableResourceManager; @@ -133,8 +128,6 @@ import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.Event; import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.IExtensionPoint; -import net.neoforged.fml.ModList; import net.neoforged.fml.ModLoader; import net.neoforged.fml.common.Mod; import net.neoforged.neoforge.client.event.ClientChatEvent; @@ -173,7 +166,6 @@ import net.neoforged.neoforge.client.gui.ClientTooltipComponentManager; import net.neoforged.neoforge.client.gui.overlay.GuiOverlayManager; import net.neoforged.neoforge.client.model.data.ModelData; -import net.neoforged.neoforge.common.I18nExtension; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.util.MutableHashedLinkedMap; @@ -181,10 +173,6 @@ import net.neoforged.neoforge.forge.snapshots.ForgeSnapshotsModClient; import net.neoforged.neoforge.gametest.GameTestHooks; import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; -import net.neoforged.neoforge.network.NetworkConstants; -import net.neoforged.neoforge.network.NetworkRegistry; -import net.neoforged.neoforge.network.ServerStatusPing; -import net.neoforged.neoforge.registries.RegistryManager; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; @@ -686,115 +674,8 @@ public static void loadLayerDefinitions(ImmutableMap.Builder builder.put(k, v.get())); } - public static void processForgeListPingData(ServerStatus packet, ServerData target) { - packet.neoForgeData().ifPresentOrElse(neoForgeData -> { - final Map mods = neoForgeData.getRemoteModData(); - final Map remoteChannels = neoForgeData.getRemoteChannels(); - final int fmlver = neoForgeData.getFMLNetworkVersion(); - - boolean fmlNetMatches = fmlver == NetworkConstants.FMLNETVERSION; - boolean channelsMatch = NetworkRegistry.checkListPingCompatibilityForClient(remoteChannels); - AtomicBoolean result = new AtomicBoolean(true); - final List extraClientMods = new ArrayList<>(); - ModList.get().forEachModContainer((modid, mc) -> mc.getCustomExtension(IExtensionPoint.DisplayTest.class).ifPresent(ext -> { - boolean foundModOnServer = ext.remoteVersionTest().test(mods.get(modid), true); - result.compareAndSet(true, foundModOnServer); - if (!foundModOnServer) { - extraClientMods.add(modid); - } - })); - boolean modsMatch = result.get(); - - final Map extraServerMods = mods.entrySet().stream().filter(e -> !Objects.equals(NetworkConstants.IGNORESERVERONLY, e.getValue())).filter(e -> !ModList.get().isLoaded(e.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); - - LOGGER.debug(CLIENTHOOKS, "Received FML ping data from server at {}: FMLNETVER={}, mod list is compatible : {}, channel list is compatible: {}, extra server mods: {}", target.ip, fmlver, modsMatch, channelsMatch, extraServerMods); - - String extraReason = null; - - if (!extraServerMods.isEmpty()) { - extraReason = "fml.menu.multiplayer.extraservermods"; - LOGGER.info(CLIENTHOOKS, I18nExtension.parseMessage(extraReason) + ": {}", extraServerMods.entrySet().stream() - .map(e -> e.getKey() + "@" + e.getValue()) - .collect(Collectors.joining(", "))); - } - if (!modsMatch) { - extraReason = "fml.menu.multiplayer.modsincompatible"; - LOGGER.info(CLIENTHOOKS, "Client has mods that are missing on server: {}", extraClientMods); - } - if (!channelsMatch) { - extraReason = "fml.menu.multiplayer.networkincompatible"; - } - - if (fmlver < NetworkConstants.FMLNETVERSION) { - extraReason = "fml.menu.multiplayer.serveroutdated"; - } - if (fmlver > NetworkConstants.FMLNETVERSION) { - extraReason = "fml.menu.multiplayer.clientoutdated"; - } - target.neoForgeData = new ExtendedServerListData("FML", extraServerMods.isEmpty() && fmlNetMatches && channelsMatch && modsMatch, mods.size(), extraReason, neoForgeData.isTruncated()); - }, () -> target.neoForgeData = new ExtendedServerListData("VANILLA", NetworkRegistry.canConnectToVanillaServer(), 0, null)); - } - private static final ResourceLocation ICON_SHEET = new ResourceLocation(NeoForgeVersion.MOD_ID, "textures/gui/icons.png"); - public static void drawForgePingInfo(JoinMultiplayerScreen gui, ServerData target, GuiGraphics guiGraphics, int x, int y, int width, int relativeMouseX, int relativeMouseY) { - int idx; - String tooltip; - if (target.neoForgeData == null) - return; - switch (target.neoForgeData.type()) { - case "FML": - if (target.neoForgeData.isCompatible()) { - idx = 0; - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.compatible", target.neoForgeData.numberOfMods()); - } else { - idx = 16; - if (target.neoForgeData.extraReason() != null) { - String extraReason = I18nExtension.parseMessage(target.neoForgeData.extraReason()); - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.incompatible.extra", extraReason); - } else { - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.incompatible"); - } - } - if (target.neoForgeData.truncated()) { - tooltip += "\n" + I18nExtension.parseMessage("fml.menu.multiplayer.truncated"); - } - break; - case "VANILLA": - if (target.neoForgeData.isCompatible()) { - idx = 48; - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.vanilla"); - } else { - idx = 80; - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.vanilla.incompatible"); - } - break; - default: - idx = 64; - tooltip = I18nExtension.parseMessage("fml.menu.multiplayer.unknown", target.neoForgeData.type()); - } - - guiGraphics.blit(ICON_SHEET, x + width - 18, y + 10, 16, 16, 0, idx, 16, 16, 256, 256); - - if (relativeMouseX > width - 15 && relativeMouseX < width && relativeMouseY > 10 && relativeMouseY < 26) { - //this is not the most proper way to do it, - //but works best here and has the least maintenance overhead - gui.setToolTip(Arrays.stream(tooltip.split("\n")).map(Component::literal).collect(Collectors.toList())); - } - } - - private static Connection getClientConnection() { - return Minecraft.getInstance().getConnection() != null ? Minecraft.getInstance().getConnection().getConnection() : null; - } - - public static void handleClientLevelClosing(ClientLevel level) { - Connection client = getClientConnection(); - // ONLY revert a non-local connection - if (client != null && !client.isMemoryConnection()) { - RegistryManager.revertToFrozen(); - } - } - public static void firePlayerLogin(MultiPlayerGameMode pc, LocalPlayer player, Connection networkManager) { NeoForge.EVENT_BUS.post(new ClientPlayerNetworkEvent.LoggingIn(pc, player, networkManager)); } diff --git a/src/main/java/net/neoforged/neoforge/client/extensions/IMenuProviderExtension.java b/src/main/java/net/neoforged/neoforge/client/extensions/IMenuProviderExtension.java new file mode 100644 index 0000000000..89a1e1fbae --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/extensions/IMenuProviderExtension.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.extensions; + +/** + * Extension type for the {@link net.minecraft.world.MenuProvider} interface. + */ +public interface IMenuProviderExtension { + + /** + * {@return {@code true} if the existing container should be closed on the client side when opening a new one, {@code false} otherwise} + * + * @implNote Returning false prevents the mouse from being (re-)centered when opening a new container. + */ + default boolean shouldTriggerClientSideContainerClosingOnOpen() { + return true; + } +} diff --git a/src/main/java/net/neoforged/neoforge/client/gui/ModMismatchDisconnectedScreen.java b/src/main/java/net/neoforged/neoforge/client/gui/ModMismatchDisconnectedScreen.java index 6ba4204339..fbe90ddadf 100644 --- a/src/main/java/net/neoforged/neoforge/client/gui/ModMismatchDisconnectedScreen.java +++ b/src/main/java/net/neoforged/neoforge/client/gui/ModMismatchDisconnectedScreen.java @@ -6,12 +6,12 @@ package net.neoforged.neoforge.client.gui; import com.mojang.blaze3d.vertex.Tesselator; +import java.net.URL; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Map.Entry; import java.util.Optional; import java.util.stream.Collectors; import net.minecraft.ChatFormatting; @@ -19,7 +19,6 @@ import net.minecraft.client.Minecraft; import net.minecraft.client.gui.GuiGraphics; import net.minecraft.client.gui.components.Button; -import net.minecraft.client.gui.components.MultiLineLabel; import net.minecraft.client.gui.narration.NarrationElementOutput; import net.minecraft.client.gui.screens.Screen; import net.minecraft.network.chat.ClickEvent; @@ -35,53 +34,31 @@ import net.neoforged.fml.loading.FMLPaths; import net.neoforged.neoforge.client.gui.widget.ScrollPanel; import net.neoforged.neoforge.common.I18nExtension; -import net.neoforged.neoforge.network.ConnectionData.ModMismatchData; -import net.neoforged.neoforge.network.NetworkRegistry; import org.apache.commons.lang3.tuple.Pair; public class ModMismatchDisconnectedScreen extends Screen { - private final Component reason; - private MultiLineLabel message = MultiLineLabel.EMPTY; private final Screen parent; private int textHeight; - private final ModMismatchData modMismatchData; private final Path modsDir; private final Path logFile; - private final int listHeight; - private final Map> presentModData; - private final List missingModData; - private final Map mismatchedModData; - private final List allModIds; - private final Map presentModUrls; - private final boolean mismatchedDataFromServer; + private final int listHeight = 140; + private final Map mismatchedChannelData; - public ModMismatchDisconnectedScreen(Screen parentScreen, Component title, Component reason, ModMismatchData modMismatchData) { + public ModMismatchDisconnectedScreen(Screen parentScreen, Component title, Map mismatchedChannelData) { super(title); this.parent = parentScreen; - this.reason = reason; - this.modMismatchData = modMismatchData; this.modsDir = FMLPaths.MODSDIR.get(); this.logFile = FMLPaths.GAMEDIR.get().resolve(Paths.get("logs", "latest.log")); - this.listHeight = modMismatchData.containsMismatches() ? 140 : 0; - this.mismatchedDataFromServer = modMismatchData.mismatchedDataFromServer(); - this.presentModData = modMismatchData.presentModData(); - this.missingModData = modMismatchData.mismatchedModData().entrySet().stream().filter(e -> e.getValue().equals(NetworkRegistry.ABSENT.version())).map(Entry::getKey).collect(Collectors.toList()); - this.mismatchedModData = modMismatchData.mismatchedModData().entrySet().stream().filter(e -> !e.getValue().equals(NetworkRegistry.ABSENT.version())).collect(Collectors.toMap(Entry::getKey, Entry::getValue)); - this.allModIds = presentModData.keySet().stream().map(ResourceLocation::getNamespace).distinct().collect(Collectors.toList()); - this.presentModUrls = ModList.get().getMods().stream().filter(info -> allModIds.contains(info.getModId())).map(info -> Pair.of(info.getModId(), (String) info.getConfig().getConfigElement("displayURL").orElse(""))).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); + this.mismatchedChannelData = mismatchedChannelData; } @Override protected void init() { - this.message = MultiLineLabel.create(this.font, this.reason, this.width - 50); - this.textHeight = this.message.getLineCount() * 9; - int listLeft = Math.max(8, this.width / 2 - 220); int listWidth = Math.min(440, this.width - 16); int upperButtonHeight = Math.min((this.height + this.listHeight + this.textHeight) / 2 + 10, this.height - 50); int lowerButtonHeight = Math.min((this.height + this.listHeight + this.textHeight) / 2 + 35, this.height - 25); - if (modMismatchData.containsMismatches()) - this.addRenderableWidget(new MismatchInfoPanel(minecraft, listWidth, listHeight, (this.height - this.listHeight) / 2, listLeft)); + this.addRenderableWidget(new MismatchInfoPanel(minecraft, listWidth, listHeight, (this.height - this.listHeight) / 2, listLeft)); int buttonWidth = Math.min(210, this.width / 2 - 20); this.addRenderableWidget(Button.builder(Component.literal(I18nExtension.parseMessage("fml.button.open.file", logFile.getFileName())), button -> Util.getPlatform().openFile(logFile.toFile())) @@ -98,14 +75,13 @@ protected void init() { @Override public void render(GuiGraphics guiGraphics, int mouseX, int mouseY, float partialTicks) { this.renderBackground(guiGraphics, mouseX, mouseY, partialTicks); - int textYOffset = modMismatchData.containsMismatches() ? 18 : 0; + int textYOffset = 18; guiGraphics.drawCenteredString(this.font, this.title, this.width / 2, (this.height - this.listHeight - this.textHeight) / 2 - textYOffset - 9 * 2, 0xAAAAAA); - this.message.renderCentered(guiGraphics, this.width / 2, (this.height - this.listHeight - this.textHeight) / 2 - textYOffset); super.render(guiGraphics, mouseX, mouseY, partialTicks); } class MismatchInfoPanel extends ScrollPanel { - private final List>> lineTable; + private final List> lineTable; private final int contentSize; private final int nameIndent = 10; private final int tableWidth = width - border * 2 - 6 - nameIndent; @@ -116,63 +92,46 @@ public MismatchInfoPanel(Minecraft client, int width, int height, int top, int l super(client, width, height, top, left); //The raw list of the strings in a table row, the components may still be too long for the final table and will be split up later. The first row element may have a style assigned to it that will be used for the whole content row. - List>> rawTable = new ArrayList<>(); - if (!missingModData.isEmpty()) { - //The header of the section, colored in gray - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.missingmods.server" : "fml.modmismatchscreen.missingmods.client")).withStyle(ChatFormatting.GRAY), null)); - //This table section contains the mod name and mod version of each mod that has a missing remote counterpart (if the mod is missing on the server, the client mod version is displayed, and vice versa) - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage("fml.modmismatchscreen.table.modname")).withStyle(ChatFormatting.UNDERLINE), Pair.of("", I18nExtension.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.youhave" : "fml.modmismatchscreen.table.youneed")))); - int i = 0; - for (ResourceLocation mod : missingModData) { - rawTable.add(Pair.of(toModNameComponent(mod, presentModData.get(mod).getLeft(), i), Pair.of("", presentModData.getOrDefault(mod, Pair.of("", "")).getRight()))); - if (++i >= 10) { - //If too many missing mod entries are present, append a line referencing how to see the full list and stop rendering any more entries - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage("fml.modmismatchscreen.additional", missingModData.size() - i)).withStyle(ChatFormatting.ITALIC), Pair.of("", ""))); - break; - } - } - rawTable.add(Pair.of(Component.literal(" "), null)); //padding - } - if (!mismatchedModData.isEmpty()) { - //The header of the table section, colored in gray - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage("fml.modmismatchscreen.mismatchedmods")).withStyle(ChatFormatting.GRAY), null)); + record Row(MutableComponent name, MutableComponent reason) {} + List rows = new ArrayList<>(); + if (!mismatchedChannelData.isEmpty()) { //This table section contains the mod name and both mod versions of each mod that has a mismatching client and server version - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage("fml.modmismatchscreen.table.modname")).withStyle(ChatFormatting.UNDERLINE), Pair.of(I18nExtension.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.youhave" : "fml.modmismatchscreen.table.serverhas"), I18nExtension.parseMessage(mismatchedDataFromServer ? "fml.modmismatchscreen.table.serverhas" : "fml.modmismatchscreen.table.youhave")))); + rows.add(new Row(Component.translatable("fml.modmismatchscreen.table.channelname"), Component.translatable("fml.modmismatchscreen.table.reason"))); int i = 0; - for (Map.Entry modData : mismatchedModData.entrySet()) { - rawTable.add(Pair.of(toModNameComponent(modData.getKey(), presentModData.get(modData.getKey()).getLeft(), i), Pair.of(presentModData.getOrDefault(modData.getKey(), Pair.of("", "")).getRight(), modData.getValue()))); + for (Map.Entry modData : mismatchedChannelData.entrySet()) { + rows.add(new Row(toChannelNameComponent(modData.getKey()), modData.getValue().copy())); if (++i >= 10) { //If too many mismatched mod entries are present, append a line referencing how to see the full list and stop rendering any more entries - rawTable.add(Pair.of(Component.literal(I18nExtension.parseMessage("fml.modmismatchscreen.additional", mismatchedModData.size() - i)).withStyle(ChatFormatting.ITALIC), Pair.of("", ""))); + rows.add(new Row(Component.literal(""), Component.translatable("fml.modmismatchscreen.additional", mismatchedChannelData.size() - i).withStyle(ChatFormatting.ITALIC))); break; } } - rawTable.add(Pair.of(Component.literal(" "), null)); //padding + rows.add(new Row(Component.literal(""), Component.literal(""))); //Add one line of padding. } - this.lineTable = rawTable.stream().flatMap(p -> splitLineToWidth(p.getKey(), p.getValue()).stream()).collect(Collectors.toList()); + this.lineTable = rows.stream().flatMap(p -> splitLineToWidth(p.name(), p.reason()).stream()).collect(Collectors.toList()); this.contentSize = lineTable.size(); } /** * Splits the raw name and version strings, making them use multiple lines if needed, to fit within the table dimensions. * The style assigned to the name element is then applied to the entire content row. - * - * @param name The first element of the content row, usually representing a table section header or the name of a mod entry - * @param versions The last two elements of the content row, usually representing the mod versions. If either one or both of them are not given, the first element may take up more space within the table. - * @return A list of table rows consisting of 3 elements each which consist of the same content as was given by the parameters, but split up to fit within the table dimensions. + * + * @param name The first element of the content row, usually representing a table section header or the name of a mod entry + * @param reason The second element of the content row, usually representing the reason why the mod is mismatched + * @return A list of table rows consisting of 2 elements each which consist of the same content as was given by the parameters, but split up to fit within the table dimensions. */ - private List>> splitLineToWidth(MutableComponent name, Pair versions) { + private List> splitLineToWidth(MutableComponent name, MutableComponent reason) { Style style = name.getStyle(); - int versionColumns = versions == null ? 0 : (versions.getLeft().isEmpty() ? (versions.getRight().isEmpty() ? 0 : 1) : 2); + int versionColumns = 1; int adaptedNameWidth = nameWidth + versionWidth * (2 - versionColumns) - 4; //the name width may be expanded when the version column string is missing List nameLines = font.split(name, adaptedNameWidth); - List clientVersionLines = font.split(Component.literal(versions != null ? versions.getLeft() : "").setStyle(style), versionWidth - 4); - List serverVersionLines = font.split(Component.literal(versions != null ? versions.getRight() : "").setStyle(style), versionWidth - 4); - List>> splitLines = new ArrayList<>(); - int rowsOccupied = Math.max(nameLines.size(), Math.max(clientVersionLines.size(), serverVersionLines.size())); + List reasonLines = font.split(reason.setStyle(style), versionWidth - 4); + List> splitLines = new ArrayList<>(); + + int rowsOccupied = Math.max(nameLines.size(), reasonLines.size()); for (int i = 0; i < rowsOccupied; i++) { - splitLines.add(Pair.of(i < nameLines.size() ? nameLines.get(i) : FormattedCharSequence.EMPTY, versions == null ? null : Pair.of(i < clientVersionLines.size() ? clientVersionLines.get(i) : FormattedCharSequence.EMPTY, i < serverVersionLines.size() ? serverVersionLines.get(i) : FormattedCharSequence.EMPTY))); + splitLines.add(Pair.of(i < nameLines.size() ? nameLines.get(i) : FormattedCharSequence.EMPTY, i < reasonLines.size() ? reasonLines.get(i) : FormattedCharSequence.EMPTY)); } return splitLines; } @@ -180,18 +139,24 @@ private List s.withHoverEvent(new HoverEvent(Action.SHOW_TEXT, Component.literal(tooltipId + (!presentModUrls.getOrDefault(modId, "").isEmpty() ? "\n" + I18nExtension.parseMessage("fml.modmismatchscreen.homepage") : ""))))) - .withStyle(s -> s.withClickEvent(!presentModUrls.getOrDefault(modId, "").isEmpty() ? new ClickEvent(ClickEvent.Action.OPEN_URL, presentModUrls.get(modId)) : null)); + + String url = ModList.get().getModContainerById(modId) + .flatMap(container -> container.getModInfo().getModURL()) + .map(URL::toString) + .orElse(""); + MutableComponent result = Component.literal(id.toString()).withStyle(ChatFormatting.YELLOW); + if (!url.isEmpty()) { + result = result.withStyle(s -> s.withHoverEvent(new HoverEvent(Action.SHOW_TEXT, Component.translatable("fml.modmismatchscreen.table.visit.mod_page", id.toString())))) + .withStyle(s -> s.withClickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, url))); + } + + return result; } @Override @@ -208,17 +173,16 @@ protected int getContentHeight() { protected void drawPanel(GuiGraphics guiGraphics, int entryRight, int relativeY, Tesselator tess, int mouseX, int mouseY) { int i = 0; - for (Pair> line : lineTable) { + for (Pair line : lineTable) { FormattedCharSequence name = line.getLeft(); - Pair versions = line.getRight(); + FormattedCharSequence reasons = line.getRight(); //Since font#draw does not respect the color of the given component, we have to read it out here and then use it as the last parameter int color = Optional.ofNullable(font.getSplitter().componentStyleAtWidth(name, 0)).map(Style::getColor).map(TextColor::getValue).orElse(0xFFFFFF); //Only indent the given name if a version string is present. This makes it easier to distinguish table section headers and mod entries - int nameLeft = left + border + (versions == null ? 0 : nameIndent); + int nameLeft = left + border + (reasons == null ? 0 : nameIndent); guiGraphics.drawString(font, name, nameLeft, relativeY + i * 12, color, false); - if (versions != null) { - guiGraphics.drawString(font, versions.getLeft(), left + border + nameIndent + nameWidth, relativeY + i * 12, color, false); - guiGraphics.drawString(font, versions.getRight(), left + border + nameIndent + nameWidth + versionWidth, relativeY + i * 12, color, false); + if (reasons != null) { + guiGraphics.drawString(font, reasons, left + border + nameIndent + nameWidth, relativeY + i * 12, color, false); } i++; diff --git a/src/main/java/net/neoforged/neoforge/client/util/DebuggingHelper.java b/src/main/java/net/neoforged/neoforge/client/util/DebuggingHelper.java new file mode 100644 index 0000000000..16ed312c1c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/client/util/DebuggingHelper.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.client.util; + +import net.minecraft.client.Minecraft; +import org.jetbrains.annotations.ApiStatus; + +/** + * Debugging helper class. + * + * @implNote This class is not intended for use in production builds, however although it is marked as internal, modders should feel free to use it during their debugging sessions. + */ +@ApiStatus.Internal +public final class DebuggingHelper { + + private DebuggingHelper() { + throw new IllegalStateException("Tried to create utility class!"); + } + + /** + * Utility method to release the mouse. + *

+ * Useful for debugging, as it allows you to move the mouse outside of the game window, if your window manager does not release it automatically, using a conditional breakpoint. + *

+ * + * @return {@code true}, always. + */ + public static boolean releaseMouse() { + Minecraft.getInstance().mouseHandler.releaseMouse(); + return true; + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java index 5d016bfbb9..ff015bfc0a 100644 --- a/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java +++ b/src/main/java/net/neoforged/neoforge/common/NeoForgeMod.java @@ -132,8 +132,6 @@ import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; import net.neoforged.neoforge.internal.versions.neoform.NeoFormVersion; import net.neoforged.neoforge.network.DualStackUtils; -import net.neoforged.neoforge.network.NetworkConstants; -import net.neoforged.neoforge.network.filters.VanillaPacketSplitter; import net.neoforged.neoforge.registries.DataPackRegistryEvent; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; @@ -464,7 +462,7 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { return uuid.toString(); }); - LOGGER.debug(NEOFORGEMOD, "Loading Network data for FML net version: {}", NetworkConstants.init()); + LOGGER.debug(NEOFORGEMOD, "Loading Network data for FML net version: {}", NeoForgeVersion.getSpec()); CrashReportCallables.registerCrashCallable("FML", NeoForgeVersion::getSpec); CrashReportCallables.registerCrashCallable("NeoForge", () -> NeoForgeVersion.getGroup() + ":" + NeoForgeVersion.getVersion()); @@ -524,7 +522,6 @@ public NeoForgeMod(IEventBus modEventBus, Dist dist, ModContainer container) { public void preInit(FMLCommonSetupEvent evt) { VersionChecker.startVersionCheck(); - VanillaPacketSplitter.register(); } public void loadComplete(FMLLoadCompleteEvent event) {} diff --git a/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java b/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java index fb1ea65bb6..9d2a014eca 100644 --- a/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java +++ b/src/main/java/net/neoforged/neoforge/common/TierSortingRegistry.java @@ -25,11 +25,13 @@ import java.util.List; import java.util.Objects; import java.util.Optional; +import java.util.function.Consumer; import java.util.stream.Collectors; -import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; import net.minecraft.resources.ResourceLocation; import net.minecraft.server.MinecraftServer; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.packs.resources.PreparableReloadListener; import net.minecraft.server.packs.resources.Resource; import net.minecraft.server.packs.resources.ResourceManager; @@ -46,12 +48,11 @@ import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.fml.loading.toposort.TopologicalSort; import net.neoforged.neoforge.client.event.ClientPlayerNetworkEvent; -import net.neoforged.neoforge.event.entity.player.PlayerEvent; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.NetworkRegistry; -import net.neoforged.neoforge.network.PacketDistributor; -import net.neoforged.neoforge.network.PlayNetworkDirection; -import net.neoforged.neoforge.network.simple.SimpleChannel; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.configuration.SyncTierSortingRegistry; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.payload.TierSortingRegistryPayload; +import net.neoforged.neoforge.network.payload.TierSortingRegistrySyncCompletePayload; import net.neoforged.neoforge.server.ServerLifecycleHooks; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -215,26 +216,15 @@ private static ResourceLocation getTierName(Object entry) { private static final List sortedTiers = new ArrayList<>(); private static final List sortedTiersUnmodifiable = Collections.unmodifiableList(sortedTiers); - private static final ResourceLocation CHANNEL_NAME = new ResourceLocation("neoforge:tier_sorting"); - private static final String PROTOCOL_VERSION = "1.0"; - private static final SimpleChannel SYNC_CHANNEL = NetworkRegistry.newSimpleChannel( - CHANNEL_NAME, () -> "1.0", - versionFromServer -> PROTOCOL_VERSION.equals(versionFromServer) || (allowVanilla() && NetworkRegistry.ACCEPTVANILLA.equals(versionFromServer)), - versionFromClient -> PROTOCOL_VERSION.equals(versionFromClient) || (allowVanilla() && NetworkRegistry.ACCEPTVANILLA.equals(versionFromClient))); - static boolean allowVanilla() { return !hasCustomTiers; } - /*package private*/ - static void init() { - SYNC_CHANNEL.registerMessage(0, SyncPacket.class, SyncPacket::encode, TierSortingRegistry::receive, TierSortingRegistry::handle, Optional.of(PlayNetworkDirection.PLAY_TO_CLIENT)); - NeoForge.EVENT_BUS.addListener(TierSortingRegistry::playerLoggedIn); + /*package private*/ static void init() { if (FMLEnvironment.dist.isClient()) ClientEvents.init(); } - /*package private*/ - static PreparableReloadListener getReloadListener() { + /*package private*/ static PreparableReloadListener getReloadListener() { return new SimplePreparableReloadListener() { final Gson gson = (new GsonBuilder()).create(); @@ -267,7 +257,7 @@ protected void apply(@NotNull JsonObject data, @NotNull ResourceManager resource } List missingTiers = tiers.values().stream().filter(tier -> !customOrder.contains(tier)).toList(); - if (missingTiers.size() > 0) + if (!missingTiers.isEmpty()) throw new IllegalStateException("Tiers missing from the ordered list: " + missingTiers.stream().map(tier -> Objects.toString(TierSortingRegistry.getName(tier))).collect(Collectors.joining(", "))); setTierOrder(customOrder); @@ -302,7 +292,6 @@ private static void setTierOrder(List tierList) { runInServerThreadIfPossible(hasServer -> { sortedTiers.clear(); sortedTiers.addAll(tierList); - if (hasServer) syncToAll(); }); } @@ -312,43 +301,21 @@ private static void runInServerThreadIfPossible(BooleanConsumer runnable) { else runnable.accept(false); } - private static void syncToAll() { - for (ServerPlayer serverPlayer : ServerLifecycleHooks.getCurrentServer().getPlayerList().getPlayers()) { - syncToPlayer(serverPlayer); - } + public static void handleSync(TierSortingRegistryPayload payload, ConfigurationPayloadContext context) { + setTierOrder(payload.tiers().stream().map(TierSortingRegistry::byName).toList()); + context.replyHandler().send(new TierSortingRegistrySyncCompletePayload()); } - private static void playerLoggedIn(PlayerEvent.PlayerLoggedInEvent event) { - if (event.getEntity() instanceof ServerPlayer serverPlayer) { - syncToPlayer(serverPlayer); - } - } - - private static void syncToPlayer(ServerPlayer serverPlayer) { - if (SYNC_CHANNEL.isRemotePresent(serverPlayer.connection.connection) && !serverPlayer.connection.connection.isMemoryConnection()) { - SYNC_CHANNEL.send(PacketDistributor.PLAYER.with(() -> serverPlayer), new SyncPacket(sortedTiers.stream().map(TierSortingRegistry::getName).toList())); - } - } - - private static SyncPacket receive(FriendlyByteBuf buffer) { - int count = buffer.readVarInt(); - List list = new ArrayList<>(); - for (int i = 0; i < count; i++) - list.add(buffer.readResourceLocation()); - return new SyncPacket(list); - } - - private static void handle(SyncPacket packet, NetworkEvent.Context context) { - setTierOrder(packet.tiers.stream().map(TierSortingRegistry::byName).toList()); - context.setPacketHandled(true); - } - - private record SyncPacket(List tiers) { - private void encode(FriendlyByteBuf buffer) { - buffer.writeVarInt(tiers.size()); - for (ResourceLocation loc : tiers) - buffer.writeResourceLocation(loc); + public static void sync(ServerConfigurationPacketListener listener, Consumer sender) { + if (listener.isVanillaConnection()) { + if (allowVanilla()) { + listener.finishCurrentTask(SyncTierSortingRegistry.TYPE); + } else { + listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + } + return; } + sender.accept(new TierSortingRegistryPayload(TierSortingRegistry.getSortedTiers().stream().map(TierSortingRegistry::getName).toList())); } private static class ClientEvents { @@ -357,7 +324,7 @@ public static void init() { } private static void clientLogInToServer(ClientPlayerNetworkEvent.LoggingIn event) { - if (event.getConnection() == null || !event.getConnection().isMemoryConnection()) + if (!event.getConnection().isMemoryConnection()) recalculateItemTiers(); } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IClientCommonPacketListenerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IClientCommonPacketListenerExtension.java new file mode 100644 index 0000000000..60ad81daca --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IClientCommonPacketListenerExtension.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.client.Minecraft; +import net.minecraft.network.Connection; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import net.neoforged.neoforge.network.registration.NetworkRegistry; + +/** + * This interface is used to extend the {@link ClientCommonPacketListener} interface. + *

+ * Its primary purpose is to expose sending logic, for transmitting packets to the server. + *

+ */ +public interface IClientCommonPacketListenerExtension { + + /** + * {@return the ClientCommonPacketListener this extension is attached to} + */ + private ClientCommonPacketListener self() { + return (ClientCommonPacketListener) this; + } + + /** + * Sends a packet to the server. + * + * @param packet The packet to send. + */ + void send(Packet packet); + + /** + * Sends a custom payload to the server. + * + * @param payload The payload to send. + */ + default void send(CustomPacketPayload payload) { + send(new ServerboundCustomPayloadPacket(payload)); + } + + /** + * Exposes the raw underlying connection. + * + * @return The raw underlying connection. + */ + Connection getConnection(); + + /** + * Exposes the raw underlying connection event loop that can be used to schedule tasks on the main thread. + * + * @return The raw underlying connection event loop. + */ + default ReentrantBlockableEventLoop getMainThreadEventLoop() { + return getMinecraft(); + } + + /** + * {@return true if the connection is to a vanilla client} + */ + default boolean isVanillaConnection() { + return NetworkRegistry.getInstance().isVanillaConnection(getConnection()); + } + + /** + * {@return true if the custom payload type with the given id is usable by this connection} + * + * @param payloadId The payload id to check + */ + default boolean isConnected(final ResourceLocation payloadId) { + return NetworkRegistry.getInstance().isConnected(self(), payloadId); + } + + /** + * {@return true if the custom payload is usable by this connection} + * + * @param payload The payload to check + */ + default boolean isConnected(final CustomPacketPayload payload) { + return isConnected(payload.id()); + } + + /** + * {@return the minecraft instance} + */ + Minecraft getMinecraft(); +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java index 8d9d2a1084..d66775e663 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IEntityExtension.java @@ -7,8 +7,11 @@ import java.util.Collection; import java.util.function.BiPredicate; +import java.util.function.Consumer; import net.minecraft.core.BlockPos; import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerPlayer; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.LivingEntity; @@ -25,8 +28,10 @@ import net.neoforged.neoforge.common.NeoForgeMod; import net.neoforged.neoforge.common.SoundAction; import net.neoforged.neoforge.common.util.INBTSerializable; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; import net.neoforged.neoforge.entity.PartEntity; import net.neoforged.neoforge.fluids.FluidType; +import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload; import org.jetbrains.annotations.Nullable; public interface IEntityExtension extends INBTSerializable { @@ -394,4 +399,16 @@ default SoundEvent getSoundFromFluidType(FluidType type, SoundAction action) { default boolean hasCustomOutlineRendering(Player player) { return false; } + + /** + * Sends the pairing data to the client. + * + * @param serverPlayer The player to send the data to. + * @param bundleBuilder Callback to add a custom payload to the packet. + */ + default void sendPairingData(ServerPlayer serverPlayer, Consumer bundleBuilder) { + if (this instanceof IEntityWithComplexSpawn) { + bundleBuilder.accept(new AdvancedAddEntityPayload(self())); + } + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IFriendlyByteBufExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IFriendlyByteBufExtension.java index 22bfdd082f..ec1366e40b 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IFriendlyByteBufExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IFriendlyByteBufExtension.java @@ -5,7 +5,17 @@ package net.neoforged.neoforge.common.extensions; +import static net.neoforged.neoforge.attachment.AttachmentInternals.addAttachmentsToTag; +import static net.neoforged.neoforge.attachment.AttachmentInternals.reconstructItemStack; + +import java.util.Collection; +import java.util.function.BiConsumer; +import java.util.function.IntFunction; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; import net.neoforged.neoforge.fluids.FluidStack; /** @@ -37,4 +47,90 @@ default void writeFluidStack(FluidStack stack) { default FluidStack readFluidStack() { return !self().readBoolean() ? FluidStack.EMPTY : FluidStack.readFromPacket(self()); } + + /** + * Writes the entries in the given set to the buffer, by first writing the count and then writing each entry. + * + * @param set The set to write + * @param writer The writer to use for writing each entry + * @param The type of the entry + * @implNote This is a convenience method for {@link FriendlyByteBuf#writeCollection(Collection, FriendlyByteBuf.Writer)}, where the callback can be a method on the entry type. + */ + default void writeObjectCollection(Collection set, BiConsumer writer) { + self().writeCollection(set, (buf, t) -> writer.accept(t, buf)); + } + + /** + * Reads an {@link ItemStack} from the current buffer, but allows for a larger count than the vanilla method, using a variable length int instead of a byte. + * + * @return The read stack + */ + default ItemStack readItemWithLargeCount() { + if (!self().readBoolean()) { + return ItemStack.EMPTY; + } else { + Item item = self().readById(BuiltInRegistries.ITEM); + int i = self().readVarInt(); + return reconstructItemStack(item, i, self().readNbt()); + } + } + + /** + * Writes an {@link ItemStack} to the current buffer, but allows for a larger count than the vanilla method, using a variable length int instead of a byte. + * + * @param stack The stack to write + * @return The buffer + */ + default FriendlyByteBuf writeItemWithLargeCount(ItemStack stack) { + if (stack.isEmpty()) { + self().writeBoolean(false); + } else { + self().writeBoolean(true); + Item item = stack.getItem(); + self().writeId(BuiltInRegistries.ITEM, item); + self().writeVarInt(stack.getCount()); + CompoundTag compoundtag = new CompoundTag(); + if (item.isDamageable(stack) || item.shouldOverrideMultiplayerNbt()) { + compoundtag = stack.getTag(); + } + compoundtag = addAttachmentsToTag(compoundtag, stack, false); + + self().writeNbt(compoundtag); + } + + return self(); + } + + /** + * Reads an array of objects from the buffer. + * + * @param builder A function that creates an array of the given size + * @param reader A function that reads an object from the buffer + * @return The array of objects + * @param The type of the objects + */ + default T[] readArray(IntFunction builder, FriendlyByteBuf.Reader reader) { + int size = self().readVarInt(); + T[] array = builder.apply(size); + for (int i = 0; i < size; i++) { + array[i] = reader.apply(self()); + } + return array; + } + + /** + * Writes an array of objects to the buffer. + * + * @param array The array of objects + * @param writer A function that writes an object to the buffer + * @return The buffer + * @param The type of the objects + */ + default FriendlyByteBuf writeArray(T[] array, FriendlyByteBuf.Writer writer) { + self().writeVarInt(array.length); + for (T t : array) { + writer.accept(self(), t); + } + return self(); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IPacketFlowExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IPacketFlowExtension.java new file mode 100644 index 0000000000..2eec57e51c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IPacketFlowExtension.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.network.protocol.PacketFlow; +import net.neoforged.fml.LogicalSide; + +/** + * Extension for {@link PacketFlow} to add some utility methods. + */ +public interface IPacketFlowExtension { + + /** + * {@return the {@link PacketFlow} this extension is applied to} + */ + default PacketFlow self() { + return (PacketFlow) this; + } + + /** + * {@return an indication of whether this {@link PacketFlow} is clientbound} + */ + default boolean isClientbound() { + return self() == PacketFlow.CLIENTBOUND; + } + + /** + * {@return an indication of whether this {@link PacketFlow} is serverbound} + */ + default boolean isServerbound() { + return self() == PacketFlow.SERVERBOUND; + } + + /** + * {@return the {@link LogicalSide} that is receiving packets in this {@link PacketFlow}} + */ + default LogicalSide getReceptionSide() { + return isServerbound() ? LogicalSide.SERVER : LogicalSide.CLIENT; + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java index a4a6722ee1..4d785598d2 100644 --- a/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerExtension.java @@ -5,13 +5,19 @@ package net.neoforged.neoforge.common.extensions; +import java.util.OptionalInt; +import java.util.function.Consumer; import net.minecraft.core.BlockPos; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.world.MenuProvider; import net.minecraft.world.entity.Entity; import net.minecraft.world.entity.player.Player; +import net.minecraft.world.inventory.MenuType; import net.minecraft.world.phys.AABB; import net.minecraft.world.phys.HitResult; import net.minecraft.world.phys.Vec3; import net.neoforged.neoforge.common.NeoForgeMod; +import net.neoforged.neoforge.network.IContainerFactory; public interface IPlayerExtension { @@ -92,4 +98,33 @@ default boolean isCloseEnough(Entity entity, double dist) { return aabb.distanceToSqr(eye) < dist * dist; } + /** + * Request to open a GUI on the client, from the server + *

+ * Refer to {@link MenuType#create(IContainerFactory)} for how to provide a function to consume + * these GUI requests on the client. + * + * @param menuProvider A supplier of container properties including the registry name of the container + * @param pos A block pos, which will be encoded into the additional data for this request + * + */ + default OptionalInt openMenu(MenuProvider menuProvider, BlockPos pos) { + return openMenu(menuProvider, buf -> buf.writeBlockPos(pos)); + } + + /** + * Request to open a GUI on the client, from the server + *

+ * Refer to {@link MenuType#create(IContainerFactory)} for how to provide a function to consume + * these GUI requests on the client. + *

+ * The maximum size for #extraDataWriter is 32600 bytes. + * + * @param menuProvider A supplier of container properties including the registry name of the container + * @param extraDataWriter Consumer to write any additional data the GUI needs + * @return The window ID of the opened GUI, or empty if the GUI could not be opened + */ + default OptionalInt openMenu(MenuProvider menuProvider, Consumer extraDataWriter) { + return OptionalInt.empty(); + } } diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerListExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerListExtension.java new file mode 100644 index 0000000000..f1360850d3 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IPlayerListExtension.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.players.PlayerList; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +/** + * Extension class for {@link PlayerList} + *

+ * This interface with its default methods allows for easy sending of payloads to all, or specific, players on the server. + *

+ */ +public interface IPlayerListExtension { + + /** + * {@return the PlayerList instance that this extension is attached to} + */ + default PlayerList self() { + return (PlayerList) this; + } + + /** + * Sends a payload to all players on the server + * + * @param payload the payload to send + */ + default void broadcastAll(CustomPacketPayload payload) { + self().broadcastAll(new ClientboundCustomPayloadPacket(payload)); + } + + /** + * Sends a payload to all players within the specific level. + * + * @param payload the payload to send + * @param targetLevel the level to send the payload to. + */ + default void broadcastAll(CustomPacketPayload payload, ResourceKey targetLevel) { + self().broadcastAll(new ClientboundCustomPayloadPacket(payload), targetLevel); + } + + /** + * Sends a payload to all players within the specific level, within a given range around the target point + * + * @param x the x coordinate of the target point + * @param y the y coordinate of the target point + * @param z the z coordinate of the target point + * @param range the range around the target point to send the payload to + * @param level the level to send the payload to + * @param payload the payload to send + */ + default void broadcast( + double x, double y, double z, double range, ResourceKey level, CustomPacketPayload payload) { + self().broadcast(null, x, y, z, range, level, new ClientboundCustomPayloadPacket(payload)); + } + + /** + * Sends a payload to all players within the specific level, within a given range around the target point, excluding the specified player. + * + * @param excludedPlayer the player to exclude from the broadcast, when null all players will receive the payload. + * @param x the x coordinate of the target point + * @param y the y coordinate of the target point + * @param z the z coordinate of the target point + * @param range the range around the target point to send the payload to + * @param level the level to send the payload to + * @param payload the payload to send + */ + default void broadcast( + Player excludedPlayer, double x, double y, double z, double range, ResourceKey level, CustomPacketPayload payload) { + self().broadcast(excludedPlayer, x, y, z, range, level, new ClientboundCustomPayloadPacket(payload)); + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IServerChunkCacheExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IServerChunkCacheExtension.java new file mode 100644 index 0000000000..d9cc7b5855 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IServerChunkCacheExtension.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.level.ServerChunkCache; +import net.minecraft.world.entity.Entity; + +/** + * Extension class for {@link ServerChunkCache} + *

+ * This interface with its default methods allows for easy sending of payloads players watching a specific entity. + *

+ */ +@SuppressWarnings("resource") +public interface IServerChunkCacheExtension { + + default ServerChunkCache self() { + return (ServerChunkCache) this; + } + + /** + * Sends a payload to all players watching the given entity. + *

+ * If the entity is a player, the payload will be sent to that player. + *

+ * + * @param entity the entity that needs to be watched to receive the payload, and the player to send the payload to if the entity is a player. + * @param payload the payload to send + */ + default void broadcastAndSend(Entity entity, CustomPacketPayload payload) { + self().broadcastAndSend(entity, new ClientboundCustomPayloadPacket(payload)); + } + + /** + * Sends a payload to all players watching the given entity. + *

+ * If the entity is a player, the payload will not be sent to that player. + *

+ * + * @param entity the entity that needs to be watched to receive the payload + * @param payload the payload to send + */ + default void broadcast(Entity entity, CustomPacketPayload payload) { + self().broadcast(entity, new ClientboundCustomPayloadPacket(payload)); + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IServerCommonPacketListenerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IServerCommonPacketListenerExtension.java new file mode 100644 index 0000000000..e5085235c9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IServerCommonPacketListenerExtension.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import javax.annotation.Nullable; +import net.minecraft.network.Connection; +import net.minecraft.network.PacketSendListener; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerCommonPacketListener; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import net.neoforged.neoforge.network.registration.NetworkRegistry; + +/** + * Extension class for {@link net.minecraft.network.protocol.common.ServerCommonPacketListener} + *

+ * This interface and its default methods is used to make sending custom payloads easier. + *

+ */ +public interface IServerCommonPacketListenerExtension { + + /** + * {@return the {@link ServerCommonPacketListener} that the extensions is attached to} + */ + private ServerCommonPacketListener self() { + return (ServerCommonPacketListener) this; + } + + /** + * Sends a packet to the client which this listener is attached to. + * + * @param packet The packet to send + */ + void send(Packet packet); + + /** + * Sends a custom payload to the client which this listener is attached to. + * + * @param packetPayload The payload to send + */ + default void send(CustomPacketPayload packetPayload) { + this.send(new ClientboundCustomPayloadPacket(packetPayload)); + } + + /** + * Sends a packet to the client which this listener is attached to. + * + * @param packet The packet to send + * @param packetSendListener The listener to call when the packet is sent + */ + void send(Packet packet, @Nullable PacketSendListener packetSendListener); + + /** + * Sends a custom payload to the client which this listener is attached to. + * + * @param packetPayload The payload to send + * @param listener The listener to call when the packet is sent + */ + default void send(CustomPacketPayload packetPayload, @Nullable PacketSendListener listener) { + this.send(new ClientboundCustomPayloadPacket(packetPayload), listener); + } + + /** + * Triggers a disconnection with the given reason. + * + * @param reason The reason for the disconnection + */ + void disconnect(Component reason); + + /** + * {@return the connection this listener is attached to} + */ + Connection getConnection(); + + /** + * {@return the main thread event loop} + */ + ReentrantBlockableEventLoop getMainThreadEventLoop(); + + /** + * {@return true if the connection is to a vanilla client} + */ + default boolean isVanillaConnection() { + return NetworkRegistry.getInstance().isVanillaConnection(getConnection()); + } + + /** + * {@return true if the custom payload type with the given id is usable by this connection} + * + * @param payloadId The payload id to check + */ + default boolean isConnected(final ResourceLocation payloadId) { + return NetworkRegistry.getInstance().isConnected(self(), payloadId); + } + + /** + * {@return true if the custom payload is usable by this connection} + * + * @param payload The payload to check + */ + default boolean isConnected(final CustomPacketPayload payload) { + return isConnected(payload.id()); + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IServerConfigurationPacketListenerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IServerConfigurationPacketListenerExtension.java new file mode 100644 index 0000000000..b94e8783c6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IServerConfigurationPacketListenerExtension.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.server.network.ConfigurationTask; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; + +/** + * Extension class for {@link ServerConfigurationPacketListener} + */ +public interface IServerConfigurationPacketListenerExtension extends IServerCommonPacketListenerExtension { + /** + * Call when a configuration task is finished + * + * @param task The task that was finished + * @implNote This forces the normally private method implementation in {@link ServerConfigurationPacketListenerImpl#finishCurrentTask(ConfigurationTask.Type)} to become public, and adds this to the signature of {@link ServerConfigurationPacketListener} + */ + void finishCurrentTask(ConfigurationTask.Type task); +} diff --git a/src/main/java/net/neoforged/neoforge/common/extensions/IServerGamePacketListenerExtension.java b/src/main/java/net/neoforged/neoforge/common/extensions/IServerGamePacketListenerExtension.java new file mode 100644 index 0000000000..2e04b790d9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/extensions/IServerGamePacketListenerExtension.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.extensions; + +import java.util.List; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; +import net.minecraft.network.protocol.game.ServerGamePacketListener; +import org.apache.commons.compress.utils.Lists; + +/** + * Extension class for {@link ServerGamePacketListener} + */ +public interface IServerGamePacketListenerExtension extends IServerCommonPacketListenerExtension { + + /** + * {@return the listener this extension is attached to} + */ + private ServerGamePacketListener self() { + return (ServerGamePacketListener) this; + } + + /** + * Sends all given payloads as a bundle to the client. + * + * @param payloads the payloads to send + */ + default void sendBundled(CustomPacketPayload... payloads) { + this.sendBundled(List.of(payloads)); + } + + /** + * Sends all given payloads as a bundle to the client. + * + * @param payloads the payloads to send + */ + default void sendBundled(Iterable payloads) { + final List> packets = Lists.newArrayList(); + for (CustomPacketPayload payload : payloads) { + packets.add(new ClientboundCustomPayloadPacket(payload)); + } + + self().send(new ClientboundBundlePacket(packets)); + } +} diff --git a/src/main/java/net/neoforged/neoforge/common/util/FriendlyByteBufUtil.java b/src/main/java/net/neoforged/neoforge/common/util/FriendlyByteBufUtil.java new file mode 100644 index 0000000000..1eb4f5bdf9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/common/util/FriendlyByteBufUtil.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.common.util; + +import io.netty.buffer.Unpooled; +import java.util.function.Consumer; +import net.minecraft.network.FriendlyByteBuf; + +/** + * Utility class for working with {@link FriendlyByteBuf}s. + */ +public class FriendlyByteBufUtil { + + private FriendlyByteBufUtil() { + throw new IllegalStateException("Tried to create utility class!"); + } + + /** + * Writes custom data to a {@link FriendlyByteBuf}, then returns the written data as a byte array. + * + * @param dataWriter The data writer. + * @return The written data. + */ + public static byte[] writeCustomData(Consumer dataWriter) { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + try { + dataWriter.accept(buf); + return buf.array(); + } finally { + buf.release(); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/entity/IEntityAdditionalSpawnData.java b/src/main/java/net/neoforged/neoforge/entity/IEntityWithComplexSpawn.java similarity index 86% rename from src/main/java/net/neoforged/neoforge/entity/IEntityAdditionalSpawnData.java rename to src/main/java/net/neoforged/neoforge/entity/IEntityWithComplexSpawn.java index f998acc315..323cdc1030 100644 --- a/src/main/java/net/neoforged/neoforge/entity/IEntityAdditionalSpawnData.java +++ b/src/main/java/net/neoforged/neoforge/entity/IEntityWithComplexSpawn.java @@ -8,10 +8,10 @@ import net.minecraft.network.FriendlyByteBuf; /** - * A interface for Entities that need extra information to be communicated + * An interface for Entities that need extra information to be communicated * between the server and client when they are spawned. */ -public interface IEntityAdditionalSpawnData { +public interface IEntityWithComplexSpawn { /** * Called by the server when constructing the spawn packet. * Data should be added to the provided stream. diff --git a/src/main/java/net/neoforged/neoforge/internal/NeoForgeStatesProvider.java b/src/main/java/net/neoforged/neoforge/internal/NeoForgeStatesProvider.java index 79fbe9f548..50806aaca0 100644 --- a/src/main/java/net/neoforged/neoforge/internal/NeoForgeStatesProvider.java +++ b/src/main/java/net/neoforged/neoforge/internal/NeoForgeStatesProvider.java @@ -10,7 +10,7 @@ import net.neoforged.fml.IModStateProvider; import net.neoforged.fml.ModLoadingPhase; import net.neoforged.fml.ModLoadingState; -import net.neoforged.neoforge.network.NetworkRegistry; +import net.neoforged.neoforge.network.registration.NetworkRegistry; import net.neoforged.neoforge.registries.GameData; import net.neoforged.neoforge.registries.RegistryManager; @@ -20,7 +20,7 @@ public class NeoForgeStatesProvider implements IModStateProvider { final ModLoadingState LOAD_REGISTRIES = ModLoadingState.withInline("LOAD_REGISTRIES", "UNFREEZE_DATA", ModLoadingPhase.GATHER, ml -> GameData.postRegisterEvents()); final ModLoadingState FREEZE = ModLoadingState.withInline("FREEZE_DATA", "LOAD_REGISTRIES", ModLoadingPhase.GATHER, ml -> GameData.freezeData()); final ModLoadingState REGISTRATION_EVENTS = ModLoadingState.withInline("REGISTRATION_EVENTS", "SIDED_SETUP", ModLoadingPhase.LOAD, ml -> RegistrationEvents.init()); - final ModLoadingState NETLOCK = ModLoadingState.withInline("NETWORK_LOCK", "COMPLETE", ModLoadingPhase.COMPLETE, ml -> NetworkRegistry.lock()); + final ModLoadingState NETLOCK = ModLoadingState.withInline("NETWORK_LOCK", "COMPLETE", ModLoadingPhase.COMPLETE, ml -> NetworkRegistry.getInstance().setup()); @Override public List getAllStates() { diff --git a/src/main/java/net/neoforged/neoforge/network/ConfigSync.java b/src/main/java/net/neoforged/neoforge/network/ConfigSync.java index 5a00faa63a..da395ec660 100644 --- a/src/main/java/net/neoforged/neoforge/network/ConfigSync.java +++ b/src/main/java/net/neoforged/neoforge/network/ConfigSync.java @@ -14,8 +14,10 @@ import net.minecraft.client.Minecraft; import net.neoforged.fml.config.ConfigTracker; import net.neoforged.fml.config.ModConfig; -import net.neoforged.neoforge.network.simple.MessageFunctions; +import net.neoforged.neoforge.network.payload.ConfigFilePayload; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public class ConfigSync { public static final ConfigSync INSTANCE = new ConfigSync(ConfigTracker.INSTANCE); private final ConfigTracker tracker; @@ -24,7 +26,7 @@ private ConfigSync(final ConfigTracker tracker) { this.tracker = tracker; } - public List> syncConfigs(boolean isLocal) { + public List syncConfigs() { final Map configData = tracker.configSets().get(ModConfig.Type.SERVER).stream().collect(Collectors.toMap(ModConfig::getFileName, mc -> { try { return Files.readAllBytes(mc.getFullPath()); @@ -32,12 +34,15 @@ public List> syncC throw new RuntimeException(e); } })); - return configData.entrySet().stream().map(e -> new MessageFunctions.LoginPacket<>("Config " + e.getKey(), new HandshakeMessages.S2CConfigData(e.getKey(), e.getValue()))).toList(); + + return configData.entrySet().stream() + .map(e -> new ConfigFilePayload(e.getValue(), e.getKey())) + .toList(); } - public void receiveSyncedConfig(final HandshakeMessages.S2CConfigData s2CConfigData, final NetworkEvent.Context contextSupplier) { + public void receiveSyncedConfig(final byte[] contents, final String fileName) { if (!Minecraft.getInstance().isLocalServer()) { - Optional.ofNullable(tracker.fileMap().get(s2CConfigData.getFileName())).ifPresent(mc -> mc.acceptSyncedConfig(s2CConfigData.getBytes())); + Optional.ofNullable(tracker.fileMap().get(fileName)).ifPresent(mc -> mc.acceptSyncedConfig(contents)); } } } diff --git a/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java b/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java new file mode 100644 index 0000000000..b40d5b2522 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/ConfigurationInitialization.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network; + +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.network.configuration.SyncConfig; +import net.neoforged.neoforge.network.configuration.SyncRegistries; +import net.neoforged.neoforge.network.configuration.SyncTierSortingRegistry; +import net.neoforged.neoforge.network.event.OnGameConfigurationEvent; +import org.jetbrains.annotations.ApiStatus; + +@Mod.EventBusSubscriber(modid = "neoforge", bus = Mod.EventBusSubscriber.Bus.MOD) +@ApiStatus.Internal +public class ConfigurationInitialization { + + @SubscribeEvent + private static void configureModdedClient(OnGameConfigurationEvent event) { + if (!event.getListener().isVanillaConnection()) { + event.register(new SyncRegistries()); + event.register(new SyncConfig(event.getListener())); + } + + event.register(new SyncTierSortingRegistry(event.getListener())); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/ConnectionData.java b/src/main/java/net/neoforged/neoforge/network/ConnectionData.java deleted file mode 100644 index 249ac6cf28..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/ConnectionData.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.ImmutableMap; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Set; -import java.util.stream.Collectors; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.ModList; -import org.apache.commons.lang3.tuple.Pair; - -public class ConnectionData { - private ImmutableMap> modData; - private ImmutableMap channels; - - /* package private */ ConnectionData(Map> modData, Map channels) { - this.modData = ImmutableMap.copyOf(modData); - this.channels = ImmutableMap.copyOf(channels); - } - - /** - * Returns the list of mods present in the remote. - * WARNING: This list is not authoritative. - * A mod missing from the list does not mean the mod isn't there, - * and similarly a mod present in the list does not mean it is there. - * People using hacked clients WILL hack the mod lists to make them look correct. - * Do not use this as an anti-cheat feature! - * - * - * @return An immutable list of MODIDs - */ - public ImmutableList getModList() { - return modData.keySet().asList(); - } - - /** - * Returns a map of mods and respective mod names and versions present in the remote. - * WARNING: This list is not authoritative. - * A mod missing from the list does not mean the mod isn't there, - * and similarly a mod present in the list does not mean it is there. - * People using hacked clients WILL hack the mod lists to make them look correct. - * Do not use this as an anti-cheat feature! - * - * - * @return An immutable map of MODIDs and their respective mod versions - */ - public ImmutableMap> getModData() { - return modData; - } - - /** - * Returns the list of impl channels present in the remote. - * WARNING: This list is not authoritative. - * A channel missing from the list does not mean the remote won't accept packets with that channel ID, - * and similarly a channel present in the list does not mean the remote won't ignore it. - * People using hacked clients WILL hack the channel list to make it look correct. - * Do not use this as an anti-cheat feature! - * - * - * @return An immutable map of channel IDs and their respective protocol versions. - */ - public ImmutableMap getChannels() { - return channels; - } - - /** - * A class for holding the mod mismatch data of a failed handshake. - * Contains a list of mismatched channels, the channels present on the side the handshake failed on, the mods with mismatching registries (if available) and the information of whether the mismatching data's origin is the server. - * - * @param mismatchedModData The data of the mismatched or missing mods/channels, consisting of the mod or channel id and the version of the respective mod. If the mod version is absent, the entry is assumed to be missing on either side. If it is present, the entry gets treated as a mismatch. - * @param presentModData The data of the channels/mods from the side the mismatch data doesn't originate from, consisting of the mod or channel id and the name and version of the respective mod. This data is useful for a proper comparison against the mismatched channel data. The data is stored like this: [id -> [modName, modVersion]] - * @param mismatchedDataFromServer Whether the mismatched data originates from the server. Note, that the mismatched data origin does not tell us if the actual mismatch check happened on the client or the server. - */ - public record ModMismatchData(Map mismatchedModData, Map> presentModData, boolean mismatchedDataFromServer) { - /** - * Creates a ModMismatchData instance from given channel mismatch data, which is processed side-aware depending on the value of mismatchedDataFromServer - * - * @param mismatchedChannels The list of channels that were listed as mismatches, either because they are missing on one side or because their versions mismatched - * @param connectionData The connection data instance responsible for collecting the server mod data. - * @param mismatchedDataFromServer Whether the mismatched data originates from the server. - */ - public static ModMismatchData channel(Map mismatchedChannels, ConnectionData connectionData, boolean mismatchedDataFromServer) { - Map mismatchedChannelData = enhanceWithModVersion(mismatchedChannels, connectionData, mismatchedDataFromServer); - Map> presentChannelData = getPresentChannelData(mismatchedChannels.keySet(), connectionData, mismatchedDataFromServer); - - return new ModMismatchData(mismatchedChannelData, presentChannelData, mismatchedDataFromServer); - } - - /** - * Creates a ModMismatchData instance from given mismatched registry entries. In this case, the mismatched data is always treated as originating from the client because registry entries missing on the server never cause the handshake to fail. - * - * @param mismatchedRegistryData The list of mismatched registries and the associated mismatched registry entries. The data is stored like this: "registryNamespace:registryPath" -> "entryNamespace:entryPath" - * @param connectionData The connection data instance responsible for collecting the server mod data. - */ - public static ModMismatchData registry(Set> mismatchedRegistryData, ConnectionData connectionData) { - List mismatchedRegistryMods = mismatchedRegistryData.stream() - .map(k -> k.location().getNamespace()) - .distinct() - .map(id -> new ResourceLocation(id, "")) - .toList(); - Map mismatchedRegistryModData = mismatchedRegistryMods.stream() - .map(id -> ModList.get().getModContainerById(id.getNamespace()) - .map(modContainer -> Pair.of(id, modContainer.getModInfo().getVersion().toString())) - .orElse(Pair.of(id, NetworkRegistry.ABSENT.version()))) - .collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - Map> presentModData = getServerSidePresentModData(mismatchedRegistryModData.keySet(), connectionData); - - return new ModMismatchData(mismatchedRegistryModData, presentModData, false); - } - - /** - * @return true if this ModMismatchData instance contains channel or registry mismatches - */ - public boolean containsMismatches() { - return mismatchedModData != null && !mismatchedModData.isEmpty(); - } - - /** - * Enhances a map of mismatched channels with the corresponding mod version. - * - * @param mismatchedChannels The original mismatched channel list, containing the id and version of the mismatched channel. - * @param connectionData The connection data instance responsible for collecting the server mod data. - * @param mismatchedDataFromServer Whether the mismatched data originates from the server. The given mismatched channel list gets processed side-aware depending on the value of this parameter. - * @return A map containing the id of the channel and the version of the corresponding mod, or NetworkRegistry.ABSENT if the channel is missing. - */ - private static Map enhanceWithModVersion(Map mismatchedChannels, ConnectionData connectionData, boolean mismatchedDataFromServer) { - Map mismatchedModVersions; - - if (mismatchedDataFromServer) //enhance with data from the server - { - mismatchedModVersions = connectionData != null ? connectionData.getModData().entrySet().stream().collect(Collectors.toMap(Entry::getKey, e -> e.getValue().getRight())) : Map.of(); - } else //enhance with data from the client - { - mismatchedModVersions = ModList.get().getMods().stream().map(info -> Pair.of(info.getModId(), info.getVersion().toString())).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - } - return mismatchedChannels.keySet().stream().map(channel -> Pair.of(channel, mismatchedChannels.get(channel).equals(NetworkRegistry.ABSENT.version()) ? NetworkRegistry.ABSENT.version() : mismatchedModVersions.getOrDefault(channel.getNamespace(), NetworkRegistry.ABSENT.version()))).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - } - - /** - * Queries the channel data from the side the mismatched data isn't from, in order to provide a map of all the present channels for a proper comparison against the mismatched channel data. - * - * @param mismatchedChannelsFilter A filter that gets used in order to only query the data of mismatched mods, because that's the only one we need for a proper comparison between mismatched and present mods. - * @param connectionData The connection data instance responsible for collecting the server mod data. - * @param mismatchedDataFromServer Whether the mismatched data originates from the server. The data gets queried from the side that the mismatch data does not originate from. - * @return The data of all relevant present channels, containing the channel id and the name and version of the corresponding mod. - */ - private static Map> getPresentChannelData(Set mismatchedChannelsFilter, ConnectionData connectionData, boolean mismatchedDataFromServer) { - Map channelData; - - if (mismatchedDataFromServer) //mismatch data comes from the server, use client channel data - { - channelData = NetworkRegistry.buildChannelVersions(); - } else //mismatch data comes from the client, use server channel data - { - channelData = connectionData != null ? connectionData.getChannels() : Map.of(); - } - return channelData.keySet().stream().filter(mismatchedChannelsFilter::contains).map(id -> getPresentModDataFromChannel(id, connectionData, mismatchedDataFromServer)).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - } - - /** - * Queries the mod data from a given channel id from the side the mismatched data isn't from. - * - * @param channel The id of the channel the mod data should be queried for. - * @param connectionData The connection data instance responsible for collecting the server mod data. - * @param mismatchedDataFromServer Whether the mismatched data originates from the server. The data gets queried from the side that the mismatch data does not originate from. - * @return The mod data corresponding to the channel id, containing the channel id and the name and version of the corresponding mod. - */ - private static Pair> getPresentModDataFromChannel(ResourceLocation channel, ConnectionData connectionData, boolean mismatchedDataFromServer) { - if (mismatchedDataFromServer) { - return ModList.get().getModContainerById(channel.getNamespace()).map(modContainer -> Pair.of(channel, Pair.of(modContainer.getModInfo().getDisplayName(), modContainer.getModInfo().getVersion().toString()))).orElse(Pair.of(channel, Pair.of(channel.getNamespace(), ""))); - } else { - Map> modData = connectionData != null ? connectionData.getModData() : Map.of(); - Pair modDataFromChannel = modData.getOrDefault(channel.getNamespace(), Pair.of(channel.getNamespace(), "")); - return Pair.of(channel, modDataFromChannel.getLeft().isEmpty() ? Pair.of(channel.getNamespace(), modDataFromChannel.getRight()) : modDataFromChannel); - } - } - - /** - * Queries the mod data from the server side. Useful in case of a registry mismatch, which always gets detected on the client. - * - * @param mismatchedModsFilter A filter that gets used in order to only query the data of mismatched mods, because that's the only one we need for a proper comparison between mismatched and present mods. - * @param connectionData The connection data instance responsible for collecting the server mod data. - * @return The mod data from the server, containing the channel id and the name and version of the corresponding mod. - */ - private static Map> getServerSidePresentModData(Set mismatchedModsFilter, ConnectionData connectionData) { - Map> serverModData = connectionData != null ? connectionData.getModData() : Map.of(); - Set modIdFilter = mismatchedModsFilter.stream().map(ResourceLocation::getNamespace).collect(Collectors.toSet()); - return serverModData.entrySet().stream().filter(e -> modIdFilter.contains(e.getKey())).collect(Collectors.toMap(e -> new ResourceLocation(e.getKey(), ""), Entry::getValue)); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/ConnectionType.java b/src/main/java/net/neoforged/neoforge/network/ConnectionType.java deleted file mode 100644 index 28a2777672..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/ConnectionType.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import java.util.function.Function; - -public enum ConnectionType { - MODDED(s -> Integer.valueOf(s.substring(NetworkConstants.FMLNETMARKER.length()))), VANILLA(s -> 0); - - private final Function versionExtractor; - - ConnectionType(Function versionExtractor) { - this.versionExtractor = versionExtractor; - } - - public static ConnectionType forVersionFlag(String vers) { - return vers.startsWith(NetworkConstants.FMLNETMARKER) ? MODDED : VANILLA; - } - - public int getFMLVersionNumber(final String fmlVersion) { - return versionExtractor.apply(fmlVersion); - } - - public boolean isVanilla() { - return this == VANILLA; - } - -} diff --git a/src/main/java/net/neoforged/neoforge/network/HandshakeHandler.java b/src/main/java/net/neoforged/neoforge/network/HandshakeHandler.java deleted file mode 100644 index 6ff543d03d..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/HandshakeHandler.java +++ /dev/null @@ -1,385 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Multimaps; -import com.mojang.authlib.GameProfile; -import it.unimi.dsi.fastutil.ints.IntArrayList; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.CancellationException; -import java.util.concurrent.CompletionException; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Future; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Function; -import java.util.function.IntSupplier; -import java.util.stream.Collectors; -import net.minecraft.core.Registry; -import net.minecraft.network.Connection; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.handshake.ClientIntentionPacket; -import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.network.ServerLoginPacketListenerImpl; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.common.util.LogMessageAdapter; -import net.neoforged.neoforge.event.entity.player.PlayerNegotiationEvent; -import net.neoforged.neoforge.network.ConnectionData.ModMismatchData; -import net.neoforged.neoforge.network.simple.MessageFunctions; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import net.neoforged.neoforge.registries.DataPackRegistriesHooks; -import net.neoforged.neoforge.registries.RegistryManager; -import net.neoforged.neoforge.registries.RegistrySnapshot; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -/** - * Instance responsible for handling the overall FML impl handshake. - * - *

An instance is created during {@link ClientIntentionPacket} handling, and attached - * to the {@link Connection#channel()} via {@link NetworkConstants#FML_HANDSHAKE_HANDLER}. - * - *

The {@link NetworkConstants#handshakeChannel} is a {@link SimpleChannel} with standard messages flowing in both directions. - * - *

The {@link #loginWrapper} transforms these messages into {@link net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket} - * and {@link ClientboundCustomQueryPacket} compatible messages, by means of wrapping. - * - *

The handshake is ticked {@code #tickLogin(NetworkManager)} from the {@link ServerLoginPacketListenerImpl#tick()} method, - * utilizing the {@code ServerLoginPacketListenerImpl.State#NEGOTIATING} state, which is otherwise unused in vanilla code. - * - *

During client to server initiation, on the server, the {@link NetworkEvent.GatherLoginPayloadsEvent} is fired, - * which solicits all registered channels at the {@link NetworkRegistry} for any - * {@link NetworkRegistry.LoginPayload} they wish to supply. - * - *

The collected {@link NetworkRegistry.LoginPayload} are sent, one per tick, via - * the {@code FMLLoginWrapper#wrapPacket(ResourceLocation, net.minecraft.impl.FriendlyByteBuf)} mechanism to the incoming client connection. Each - * packet is indexed via {@link ServerboundCustomQueryPacket#getTransactionId()}, which is - * the only mechanism available for tracking request/response pairs. - * - *

Each packet sent from the server should be replied by the client, though not necessarily in sent order. The reply - * should contain the index of the server's packet it is replying to. The {@link LoginWrapper} class handles indexing - * replies correctly automatically. - * - *

Once all packets have been dispatched, we wait for all replies to be received. Once all replies are received, the - * final login phase will commence. - */ -public class HandshakeHandler { - static final Marker FMLHSMARKER = MarkerManager.getMarker("FMLHANDSHAKE").setParents(NetworkConstants.NETWORK); - private static final Logger LOGGER = LogManager.getLogger(); - - private static final LoginWrapper loginWrapper = new LoginWrapper(); - - /** - * Create a new handshake instance. Called when connection is first created during the {@link ClientIntentionPacket} - * handling. - * - * @param manager The impl manager for this connection - * @param direction The {@link PlayNetworkDirection} for this connection: {@link LoginNetworkDirection#LOGIN_TO_SERVER} or {@link LoginNetworkDirection#LOGIN_TO_CLIENT} - */ - static void registerHandshake(Connection manager, LoginNetworkDirection direction) { - manager.channel().attr(NetworkConstants.FML_HANDSHAKE_HANDLER).compareAndSet(null, new HandshakeHandler(manager, direction)); - } - - static boolean tickLogin(Connection networkManager) { - return networkManager.channel().attr(NetworkConstants.FML_HANDSHAKE_HANDLER).get().tickServer(); - } - - private final List messageList; - - private final IntArrayList sentMessages = new IntArrayList(); - - private final LoginNetworkDirection direction; - private final Connection manager; - private int packetPosition; - private Map registrySnapshots; - private Set registriesToReceive; - private boolean negotiationStarted = false; - private final List> pendingFutures = new ArrayList<>(); - - private HandshakeHandler(Connection networkManager, LoginNetworkDirection side) { - this.direction = side; - this.manager = networkManager; - if (networkManager.isMemoryConnection()) { - this.messageList = NetworkRegistry.gatherLoginPayloads(this.direction, true); - LOGGER.debug(FMLHSMARKER, "Starting local connection."); - } else if (NetworkHooks.getConnectionType(() -> this.manager) == ConnectionType.VANILLA) { - this.messageList = Collections.emptyList(); - LOGGER.debug(FMLHSMARKER, "Starting new vanilla impl connection."); - } else { - this.messageList = NetworkRegistry.gatherLoginPayloads(this.direction, false); - LOGGER.debug(FMLHSMARKER, "Starting new modded impl connection. Found {} messages to dispatch.", this.messageList.size()); - } - } - - @FunctionalInterface - public interface HandshakeConsumer { - void accept(HandshakeHandler handler, MSG msg, NetworkEvent.Context context); - } - - /** - * Transforms a two-argument instance method reference into a {@link BiConsumer} based on the {@link #getHandshake(NetworkEvent.Context)} function. - * - * This should only be used for login message types. - * - * @param consumer A two argument instance method reference - * @param message type - * @return A {@link BiConsumer} for use in message handling - */ - public static MessageFunctions.MessageConsumer consumerFor(HandshakeConsumer consumer) { - return (m, c) -> consumer.accept(getHandshake(c), m, c); - } - - /** - * Transforms a two-argument instance method reference into a {@link BiConsumer} {@link #consumerFor(HandshakeConsumer)}, first calling the {@link #handleIndexedMessage(IntSupplier, NetworkEvent.Context)} - * method to handle index tracking. Used for client to server replies. - * - * This should only be used for login messages. - * - * @param next The method reference to call after index handling - * @param message type - * @return A {@link BiConsumer} for use in message handling - */ - public static MessageFunctions.MessageConsumer indexFirst(HandshakeConsumer next) { - final MessageFunctions.MessageConsumer loginIndexedMessageSupplierBiConsumer = consumerFor(HandshakeHandler::handleIndexedMessage); - return loginIndexedMessageSupplierBiConsumer.andThen(consumerFor(next)); - } - - /** - * Retrieve the handshake from the {@link NetworkEvent.Context} - * - * @param contextSupplier the {@link NetworkEvent.Context} - * @return The handshake handler for the connection - */ - private static HandshakeHandler getHandshake(NetworkEvent.Context contextSupplier) { - return contextSupplier.attr(NetworkConstants.FML_HANDSHAKE_HANDLER).get(); - } - - void handleServerModListOnClient(HandshakeMessages.S2CModList serverModList, NetworkEvent.Context c) { - LOGGER.debug(FMLHSMARKER, "Logging into server with mod list [{}]", String.join(", ", serverModList.getModList())); - Map mismatchedChannels = NetworkRegistry.validateClientChannels(serverModList.getChannels()); - c.setPacketHandled(true); - //The connection data needs to be modified before a new ModMismatchData instance could be constructed - NetworkHooks.appendConnectionData(c.getNetworkManager(), serverModList.getModList().stream().collect(Collectors.toMap(Function.identity(), s -> Pair.of("", ""))), serverModList.getChannels()); - if (!mismatchedChannels.isEmpty()) { - LOGGER.error(FMLHSMARKER, "Terminating connection with server, mismatched mod list"); - //Populate the mod mismatch attribute with a new mismatch data instance to indicate that the disconnect happened due to a mod mismatch - c.getNetworkManager().channel().attr(NetworkConstants.FML_MOD_MISMATCH_DATA).set(ModMismatchData.channel(mismatchedChannels, NetworkHooks.getConnectionData(c.getNetworkManager()), true)); - c.getNetworkManager().disconnect(Component.literal("Connection closed - mismatched mod channel list")); - return; - } - // Validate synced custom datapack registries, client cannot be missing any present on the server. - List missingDataPackRegistries = new ArrayList<>(); - Set>> clientDataPackRegistries = DataPackRegistriesHooks.getSyncedCustomRegistries(); - for (ResourceKey> key : serverModList.getCustomDataPackRegistries()) { - if (!clientDataPackRegistries.contains(key)) { - ResourceLocation location = key.location(); - LOGGER.error(FMLHSMARKER, "Missing required datapack registry: {}", location); - missingDataPackRegistries.add(key.location().toString()); - } - } - if (!missingDataPackRegistries.isEmpty()) { - c.getNetworkManager().disconnect(Component.translatable("fml.menu.multiplayer.missingdatapackregistries", String.join(", ", missingDataPackRegistries))); - return; - } - NetworkConstants.handshakeChannel.reply(new HandshakeMessages.C2SModListReply(), c); - - LOGGER.debug(FMLHSMARKER, "Accepted server connection"); - // Set the modded marker on the channel so we know we got packets - c.getNetworkManager().channel().attr(NetworkConstants.FML_NETVERSION).set(NetworkConstants.NETVERSION); - - this.registriesToReceive = new HashSet<>(serverModList.getRegistries()); - this.registrySnapshots = Maps.newHashMap(); - LOGGER.debug("Expecting {} registries: {}", () -> this.registriesToReceive.size(), () -> this.registriesToReceive); - } - - void handleModData(HandshakeMessages.S2CModData serverModData, NetworkEvent.Context c) { - c.getNetworkManager().channel().attr(NetworkConstants.FML_CONNECTION_DATA).set(new ConnectionData(serverModData.getMods(), new HashMap<>())); - c.setPacketHandled(true); - } - - void handleIndexedMessage(MSG message, NetworkEvent.Context c) { - LOGGER.debug(FMLHSMARKER, "Received client indexed reply {} of type {}", message.getAsInt(), message.getClass().getName()); - boolean removed = this.sentMessages.removeIf(i -> i == message.getAsInt()); - if (!removed) { - LOGGER.error(FMLHSMARKER, "Recieved unexpected index {} in client reply", message.getAsInt()); - } - } - - void handleClientModListOnServer(HandshakeMessages.C2SModListReply clientModList, NetworkEvent.Context c) { - LOGGER.debug(FMLHSMARKER, "Received client connection with modlist [{}]", String.join(", ", clientModList.getModList())); - Map mismatchedChannels = NetworkRegistry.validateServerChannels(clientModList.getChannels()); - c.getNetworkManager().channel().attr(NetworkConstants.FML_CONNECTION_DATA) - .set(new ConnectionData(clientModList.getModList().stream().collect(Collectors.toMap(Function.identity(), s -> Pair.of("", ""))), clientModList.getChannels())); - c.setPacketHandled(true); - if (!mismatchedChannels.isEmpty()) { - LOGGER.error(FMLHSMARKER, "Terminating connection with client, mismatched mod list"); - NetworkConstants.handshakeChannel.reply(new HandshakeMessages.S2CChannelMismatchData(mismatchedChannels), c); - c.getNetworkManager().disconnect(Component.literal("Connection closed - mismatched mod channel list")); - return; - } - LOGGER.debug(FMLHSMARKER, "Accepted client connection mod list"); - } - - void handleModMismatchData(HandshakeMessages.S2CChannelMismatchData modMismatchData, NetworkEvent.Context c) { - if (!modMismatchData.getMismatchedChannelData().isEmpty()) { - LOGGER.error(FMLHSMARKER, "Channels [{}] rejected their client side version number", - modMismatchData.getMismatchedChannelData().keySet().stream().map(Object::toString).collect(Collectors.joining(","))); - LOGGER.error(FMLHSMARKER, "Terminating connection with server, mismatched mod list"); - c.setPacketHandled(true); - //Populate the mod mismatch attribute with a new mismatch data instance to indicate that the disconnect happened due to a mod mismatch - c.getNetworkManager().channel().attr(NetworkConstants.FML_MOD_MISMATCH_DATA).set(ModMismatchData.channel(modMismatchData.getMismatchedChannelData(), NetworkHooks.getConnectionData(c.getNetworkManager()), false)); - c.getNetworkManager().disconnect(Component.literal("Connection closed - mismatched mod channel list")); - } - } - - void handleRegistryMessage(final HandshakeMessages.S2CRegistry registryPacket, final NetworkEvent.Context contextSupplier) { - LOGGER.debug(FMLHSMARKER, "Received registry packet for {}", registryPacket.getRegistryName()); - this.registriesToReceive.remove(registryPacket.getRegistryName()); - this.registrySnapshots.put(registryPacket.getRegistryName(), registryPacket.getSnapshot()); - - boolean continueHandshake = true; - if (this.registriesToReceive.isEmpty()) { - continueHandshake = handleRegistryLoading(contextSupplier); - } - // The handshake reply isn't sent until we have processed the message - contextSupplier.setPacketHandled(true); - if (!continueHandshake) { - LOGGER.error(FMLHSMARKER, "Connection closed, not continuing handshake"); - } else { - NetworkConstants.handshakeChannel.reply(new HandshakeMessages.C2SAcknowledge(), contextSupplier); - } - } - - private boolean handleRegistryLoading(final NetworkEvent.Context contextSupplier) { - AtomicBoolean successfulConnection = new AtomicBoolean(false); - AtomicReference>> registryMismatches = new AtomicReference<>(); - var future = contextSupplier.enqueueWork(() -> { - LOGGER.debug(FMLHSMARKER, "Injecting registry snapshot from server."); - final Set> missingData = RegistryManager.applySnapshot(registrySnapshots, false, false); - LOGGER.debug(FMLHSMARKER, "Snapshot injected."); - if (!missingData.isEmpty()) { - Multimap missingRegs = missingData.stream() - .collect(Multimaps.toMultimap(ResourceKey::registry, ResourceKey::location, () -> Multimaps.newListMultimap(new HashMap<>(), ArrayList::new))); - LOGGER.error(FMLHSMARKER, "Missing registry data for impl connection:\n{}", LogMessageAdapter.adapt(sb -> missingRegs.forEach((reg, entry) -> sb.append("\t").append(reg).append(": ").append(entry).append('\n')))); - } - successfulConnection.set(missingData.isEmpty()); - registryMismatches.set(missingData); - }); - LOGGER.debug(FMLHSMARKER, "Waiting for registries to load."); - try { - future.join(); - } catch (CancellationException | CompletionException e) { - LOGGER.error(FMLHSMARKER, "Internal error when loading registry data", e); - this.manager.disconnect(Component.literal("Internal error when loading registry data: " + e)); - return false; - } - if (successfulConnection.get()) { - LOGGER.debug(FMLHSMARKER, "Registry load complete, continuing handshake."); - } else { - LOGGER.error(FMLHSMARKER, "Failed to load registry, closing connection."); - //Populate the mod mismatch attribute with a new mismatch data instance to indicate that the disconnect happened due to a mod mismatch - this.manager.channel().attr(NetworkConstants.FML_MOD_MISMATCH_DATA).set(ModMismatchData.registry(registryMismatches.get(), NetworkHooks.getConnectionData(contextSupplier.getNetworkManager()))); - this.manager.disconnect(Component.literal("Failed to synchronize registry data from server, closing connection")); - } - return successfulConnection.get(); - } - - void handleClientAck(final HandshakeMessages.C2SAcknowledge msg, final NetworkEvent.Context contextSupplier) { - LOGGER.debug(FMLHSMARKER, "Received acknowledgement from client"); - contextSupplier.setPacketHandled(true); - } - - void handleConfigSync(final HandshakeMessages.S2CConfigData msg, final NetworkEvent.Context contextSupplier) { - LOGGER.debug(FMLHSMARKER, "Received config sync from server"); - ConfigSync.INSTANCE.receiveSyncedConfig(msg, contextSupplier); - contextSupplier.setPacketHandled(true); - NetworkConstants.handshakeChannel.reply(new HandshakeMessages.C2SAcknowledge(), contextSupplier); - } - - /** - * FML will send packets, from Server to Client, from the messages queue until the queue is drained. Each message - * will be indexed, and placed into the "pending acknowledgement" queue. - * - * As indexed packets are received at the server, they will be removed from the "pending acknowledgement" queue. - * - * Once the pending queue is drained, this method returns true - indicating that login processing can proceed to - * the next step. - * - * @return true if there is no more need to tick this login connection. - */ - public boolean tickServer() { - if (!negotiationStarted) { - GameProfile profile = ((ServerLoginPacketListenerImpl) manager.getPacketListener()).authenticatedProfile; - PlayerNegotiationEvent event = new PlayerNegotiationEvent(manager, profile, pendingFutures); - NeoForge.EVENT_BUS.post(event); - negotiationStarted = true; - } - - if (packetPosition < messageList.size()) { - NetworkRegistry.LoginPayload message = messageList.get(packetPosition); - - LOGGER.debug(FMLHSMARKER, "Sending ticking packet info '{}' to '{}' sequence {}", message.getMessageContext(), message.getChannelName(), packetPosition); - if (message.needsResponse()) - sentMessages.add(packetPosition); - loginWrapper.sendServerToClientLoginPacket(message.getChannelName(), message.getData(), packetPosition, this.manager); - packetPosition++; - } - - pendingFutures.removeIf(future -> { - if (!future.isDone()) { - return false; - } - - try { - future.get(); - } catch (ExecutionException ex) { - LOGGER.error("Error during negotiation", ex.getCause()); - } catch (CancellationException | InterruptedException ex) { - // no-op - } - - return true; - }); - - // we're done when sentMessages is empty - if (sentMessages.isEmpty() && packetPosition >= messageList.size() - 1 && pendingFutures.isEmpty()) { - // clear ourselves - we're done! - this.manager.channel().attr(NetworkConstants.FML_HANDSHAKE_HANDLER).set(null); - LOGGER.debug(FMLHSMARKER, "Handshake complete!"); - return true; - } - return false; - } - - /** - * Helper method to determine if the S2C packet at the given packet position needs a response in form of a packet handled in {@link HandshakeHandler#handleIndexedMessage} for the handshake to progress. - * - * @param mgr The impl manager for this connection - * @param packetPosition The packet position of the packet that the status is queried of - * @return true if the packet at the given packet position needs a response and thus may stop the handshake from progressing - */ - public static boolean packetNeedsResponse(Connection mgr, int packetPosition) { - HandshakeHandler handler = mgr.channel().attr(NetworkConstants.FML_HANDSHAKE_HANDLER).get(); - if (handler != null) { - return handler.sentMessages.contains(packetPosition); - } - return false; - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/HandshakeMessages.java b/src/main/java/net/neoforged/neoforge/network/HandshakeMessages.java deleted file mode 100644 index 0bbcde2eef..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/HandshakeMessages.java +++ /dev/null @@ -1,327 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import com.google.common.collect.Maps; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.stream.Collectors; -import net.minecraft.core.Registry; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceKey; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.ModList; -import net.neoforged.neoforge.client.gui.ModMismatchDisconnectedScreen; -import net.neoforged.neoforge.network.simple.SimpleLoginMessage; -import net.neoforged.neoforge.registries.DataPackRegistriesHooks; -import net.neoforged.neoforge.registries.RegistryManager; -import net.neoforged.neoforge.registries.RegistrySnapshot; -import net.neoforged.neoforgespi.language.IModInfo; -import org.apache.commons.lang3.tuple.Pair; -import org.jetbrains.annotations.Nullable; - -public class HandshakeMessages { - static abstract class LoginIndexedMessage implements SimpleLoginMessage { - private int loginIndex; - - public void setLoginIndex(final int loginIndex) { - this.loginIndex = loginIndex; - } - - public int getLoginIndex() { - return loginIndex; - } - } - - /** - * Server to client "list of mods". Always first handshake message after the data sent by S2CModData. - */ - public static class S2CModList extends LoginIndexedMessage { - private List mods; - private Map channels; - private List registries; - private final List>> dataPackRegistries; - - public S2CModList() { - this.mods = ModList.get().getMods().stream().map(IModInfo::getModId).collect(Collectors.toList()); - this.channels = NetworkRegistry.buildChannelVersions(); - this.registries = RegistryManager.getRegistryNamesForSyncToClient(); - this.dataPackRegistries = List.copyOf(DataPackRegistriesHooks.getSyncedCustomRegistries()); - } - - private S2CModList(List mods, Map channels, List registries, List>> dataPackRegistries) { - this.mods = mods; - this.channels = channels; - this.registries = registries; - this.dataPackRegistries = dataPackRegistries; - } - - public static S2CModList decode(FriendlyByteBuf input) { - List mods = new ArrayList<>(); - int len = input.readVarInt(); - for (int x = 0; x < len; x++) - mods.add(input.readUtf(0x100)); - - Map channels = new HashMap<>(); - len = input.readVarInt(); - for (int x = 0; x < len; x++) - channels.put(input.readResourceLocation(), input.readUtf(0x100)); - - List registries = new ArrayList<>(); - len = input.readVarInt(); - for (int x = 0; x < len; x++) - registries.add(input.readResourceLocation()); - - List>> dataPackRegistries = input.readCollection(ArrayList::new, buf -> ResourceKey.createRegistryKey(buf.readResourceLocation())); - return new S2CModList(mods, channels, registries, dataPackRegistries); - } - - @Override - public void encode(FriendlyByteBuf output) { - output.writeVarInt(mods.size()); - mods.forEach(m -> output.writeUtf(m, 0x100)); - - output.writeVarInt(channels.size()); - channels.forEach((k, v) -> { - output.writeResourceLocation(k); - output.writeUtf(v, 0x100); - }); - - output.writeVarInt(registries.size()); - registries.forEach(output::writeResourceLocation); - - Set>> dataPackRegistries = DataPackRegistriesHooks.getSyncedCustomRegistries(); - output.writeCollection(dataPackRegistries, (buf, key) -> buf.writeResourceLocation(key.location())); - } - - public List getModList() { - return mods; - } - - public List getRegistries() { - return this.registries; - } - - public Map getChannels() { - return this.channels; - } - - /** - * @return list of ids of non-vanilla syncable datapack registries on the server. - */ - public List>> getCustomDataPackRegistries() { - return this.dataPackRegistries; - } - } - - /** - * Prefixes S2CModList by sending additional data about the mods installed on the server to the client - * The mod data is stored as follows: [modId -> [modName, modVersion]] - */ - public static class S2CModData extends LoginIndexedMessage { - private final Map> mods; - - public S2CModData() { - this.mods = ModList.get().getMods().stream().collect(Collectors.toMap(IModInfo::getModId, info -> Pair.of(info.getDisplayName(), info.getVersion().toString()))); - } - - private S2CModData(Map> mods) { - this.mods = mods; - } - - public static S2CModData decode(FriendlyByteBuf input) { - Map> mods = input.readMap(o -> o.readUtf(0x100), o -> Pair.of(o.readUtf(0x100), o.readUtf(0x100))); - return new S2CModData(mods); - } - - @Override - public void encode(FriendlyByteBuf output) { - output.writeMap(mods, (o, s) -> o.writeUtf(s, 0x100), (o, p) -> { - o.writeUtf(p.getLeft(), 0x100); - o.writeUtf(p.getRight(), 0x100); - }); - } - - public Map> getMods() { - return mods; - } - } - - public static class C2SModListReply extends LoginIndexedMessage { - private List mods; - private Map channels; - private Map registries; - - public C2SModListReply() { - this.mods = ModList.get().getMods().stream().map(IModInfo::getModId).collect(Collectors.toList()); - this.channels = NetworkRegistry.buildChannelVersions(); - this.registries = Maps.newHashMap(); //TODO: Fill with known hashes, which requires keeping a file cache - } - - private C2SModListReply(List mods, Map channels, Map registries) { - this.mods = mods; - this.channels = channels; - this.registries = registries; - } - - public static C2SModListReply decode(FriendlyByteBuf input) { - List mods = new ArrayList<>(); - int len = input.readVarInt(); - for (int x = 0; x < len; x++) - mods.add(input.readUtf(0x100)); - - Map channels = new HashMap<>(); - len = input.readVarInt(); - for (int x = 0; x < len; x++) - channels.put(input.readResourceLocation(), input.readUtf(0x100)); - - Map registries = new HashMap<>(); - len = input.readVarInt(); - for (int x = 0; x < len; x++) - registries.put(input.readResourceLocation(), input.readUtf(0x100)); - - return new C2SModListReply(mods, channels, registries); - } - - @Override - public void encode(FriendlyByteBuf output) { - output.writeVarInt(mods.size()); - mods.forEach(m -> output.writeUtf(m, 0x100)); - - output.writeVarInt(channels.size()); - channels.forEach((k, v) -> { - output.writeResourceLocation(k); - output.writeUtf(v, 0x100); - }); - - output.writeVarInt(registries.size()); - registries.forEach((k, v) -> { - output.writeResourceLocation(k); - output.writeUtf(v, 0x100); - }); - } - - public List getModList() { - return mods; - } - - public Map getRegistries() { - return this.registries; - } - - public Map getChannels() { - return this.channels; - } - } - - public static class C2SAcknowledge extends LoginIndexedMessage { - @Override - public void encode(FriendlyByteBuf buf) { - - } - - public static C2SAcknowledge decode(FriendlyByteBuf buf) { - return new C2SAcknowledge(); - } - } - - public static class S2CRegistry extends LoginIndexedMessage { - private final ResourceLocation registryName; - @Nullable - private final RegistrySnapshot snapshot; - - public S2CRegistry(final ResourceLocation name, @Nullable RegistrySnapshot snapshot) { - this.registryName = name; - this.snapshot = snapshot; - } - - @Override - public void encode(final FriendlyByteBuf buffer) { - buffer.writeResourceLocation(registryName); - buffer.writeBoolean(hasSnapshot()); - if (hasSnapshot()) - buffer.writeBytes(snapshot.getPacketData()); - } - - public static S2CRegistry decode(final FriendlyByteBuf buffer) { - ResourceLocation name = buffer.readResourceLocation(); - RegistrySnapshot snapshot = null; - if (buffer.readBoolean()) - snapshot = RegistrySnapshot.read(buffer); - return new S2CRegistry(name, snapshot); - } - - public ResourceLocation getRegistryName() { - return registryName; - } - - public boolean hasSnapshot() { - return snapshot != null; - } - - @Nullable - public RegistrySnapshot getSnapshot() { - return snapshot; - } - } - - public static class S2CConfigData extends LoginIndexedMessage { - private final String fileName; - private final byte[] fileData; - - public S2CConfigData(final String configFileName, final byte[] configFileData) { - this.fileName = configFileName; - this.fileData = configFileData; - } - - @Override - public void encode(final FriendlyByteBuf buffer) { - buffer.writeUtf(this.fileName); - buffer.writeByteArray(this.fileData); - } - - public static S2CConfigData decode(final FriendlyByteBuf buffer) { - return new S2CConfigData(buffer.readUtf(32767), buffer.readByteArray()); - } - - public String getFileName() { - return fileName; - } - - public byte[] getBytes() { - return fileData; - } - } - - /** - * Notifies the client of a channel mismatch on the server, so a {@link ModMismatchDisconnectedScreen} is used to notify the user of the disconnection. - * This packet also sends the data of a channel mismatch (currently, the ids and versions of the mismatched channels) to the client for it to display the correct information in said screen. - */ - public static class S2CChannelMismatchData extends LoginIndexedMessage { - private final Map mismatchedChannelData; - - public S2CChannelMismatchData(Map mismatchedChannelData) { - this.mismatchedChannelData = mismatchedChannelData; - } - - public static S2CChannelMismatchData decode(FriendlyByteBuf input) { - Map mismatchedMods = input.readMap(i -> new ResourceLocation(i.readUtf(0x100)), i -> i.readUtf(0x100)); - - return new S2CChannelMismatchData(mismatchedMods); - } - - public void encode(FriendlyByteBuf output) { - output.writeMap(mismatchedChannelData, (o, r) -> o.writeUtf(r.toString(), 0x100), (o, v) -> o.writeUtf(v, 0x100)); - } - - public Map getMismatchedChannelData() { - return mismatchedChannelData; - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/ICustomPacketPayloadWithBuffer.java b/src/main/java/net/neoforged/neoforge/network/ICustomPacketPayloadWithBuffer.java deleted file mode 100644 index 8d9807cb41..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/ICustomPacketPayloadWithBuffer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.common.custom.CustomPacketPayload; - -/** - * Forge extension interface to deal with custom forge query payloads. - */ -public interface ICustomPacketPayloadWithBuffer extends CustomPacketPayload { - - /** - * The buffer that created the query payload. - * - * @return The buffer - */ - FriendlyByteBuf buffer(); - - /** - * The index of the internal packet stored in the payload. - * - * @return The index of the internal packet - */ - int packetIndex(); - - /** - * The network direction in which this payload can be transmitted. - * - * @param packet The packet for which to determine the network direction if this payload was transmitted. - * @return The network direction for this payload. - */ - default PlayNetworkDirection getDirection(Packet packet) { - return PlayNetworkDirection.directionForPayload(packet.getClass()); - } - -} diff --git a/src/main/java/net/neoforged/neoforge/network/ICustomQueryPayloadWithBuffer.java b/src/main/java/net/neoforged/neoforge/network/ICustomQueryPayloadWithBuffer.java deleted file mode 100644 index 190cae5649..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/ICustomQueryPayloadWithBuffer.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload; -import net.minecraft.network.protocol.login.custom.CustomQueryPayload; - -/** - * Forge extension interface to deal with custom forge query payloads. - */ -public interface ICustomQueryPayloadWithBuffer extends CustomQueryPayload, CustomQueryAnswerPayload { - - /** - * The buffer that created the query payload. - * - * @return The buffer - */ - FriendlyByteBuf buffer(); - - /** - * The internal packet processing index for the query. - * - * @return The packet index. - */ - int packetIndex(); - - /** - * The network direction in which this payload can be transmitted. - * - * @param packet The packet to get the network direction for if this payload was transmitted - * @return The network direction for this payload. - */ - default LoginNetworkDirection getDirection(Packet packet) { - return LoginNetworkDirection.directionForPayload(packet.getClass()); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/INetworkDirection.java b/src/main/java/net/neoforged/neoforge/network/INetworkDirection.java deleted file mode 100644 index 2e90900ef9..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/INetworkDirection.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.LogicalSide; - -public interface INetworkDirection> { - TDirection reply(); - - LogicalSide getOriginationSide(); - - LogicalSide getReceptionSide(); - - Packet buildPacket(PacketData packetData, ResourceLocation channelName); - - record PacketData(FriendlyByteBuf buffer, int index) {} -} diff --git a/src/main/java/net/neoforged/neoforge/network/LoginNetworkDirection.java b/src/main/java/net/neoforged/neoforge/network/LoginNetworkDirection.java deleted file mode 100644 index 5e4fbbf96e..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/LoginNetworkDirection.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; -import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.LogicalSide; -import net.neoforged.neoforge.network.custom.payload.SimpleQueryPayload; - -public enum LoginNetworkDirection implements INetworkDirection { - LOGIN_TO_SERVER(NetworkEvent.ClientCustomPayloadLoginEvent::new, LogicalSide.CLIENT, ServerboundCustomQueryAnswerPacket.class, 1, (d, i, n) -> new ServerboundCustomQueryAnswerPacket(i, SimpleQueryPayload.outbound(d, i, n))), - LOGIN_TO_CLIENT(NetworkEvent.ServerCustomPayloadLoginEvent::new, LogicalSide.SERVER, ClientboundCustomQueryPacket.class, 0, (d, i, n) -> new ClientboundCustomQueryPacket(i, SimpleQueryPayload.outbound(d, i, n))); - - private final BiFunction eventSupplier; - private final LogicalSide logicalSide; - private final Class> packetClass; - private final int otherWay; - private final Factory factory; - - private static final Reference2ReferenceArrayMap>, LoginNetworkDirection> PACKET_LOOKUP = Stream.of(values()).collect(Collectors.toMap(LoginNetworkDirection::getPacketClass, Function.identity(), (m1, m2) -> m1, Reference2ReferenceArrayMap::new)); - - private LoginNetworkDirection(BiFunction eventSupplier, LogicalSide logicalSide, Class> clazz, int i, Factory factory) { - this.eventSupplier = eventSupplier; - this.logicalSide = logicalSide; - this.packetClass = clazz; - this.otherWay = i; - this.factory = factory; - } - - private Class> getPacketClass() { - return packetClass; - } - - public static > LoginNetworkDirection directionForPayload(Class customPacket) { - return PACKET_LOOKUP.get(customPacket); - } - - public LoginNetworkDirection reply() { - return LoginNetworkDirection.values()[this.otherWay]; - } - - public NetworkEvent getEvent(final ICustomQueryPayloadWithBuffer buffer, final NetworkEvent.Context context) { - return this.eventSupplier.apply(buffer, context); - } - - public LogicalSide getOriginationSide() { - return logicalSide; - } - - public LogicalSide getReceptionSide() { - return reply().logicalSide; - }; - - @Override - public Packet buildPacket(PacketData packetData, ResourceLocation channelName) { - return this.factory.create(packetData.buffer(), packetData.index(), channelName); - } - - private interface Factory> { - T create(FriendlyByteBuf data, int index, ResourceLocation channelName); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/LoginWrapper.java b/src/main/java/net/neoforged/neoforge/network/LoginWrapper.java deleted file mode 100644 index f072f043e1..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/LoginWrapper.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import io.netty.buffer.Unpooled; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.event.EventNetworkChannel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.ApiStatus; - -/** - * Wrapper for custom login packets. Transforms unnamed login channel messages into channels dispatched the same - * as regular custom packets. - */ -public class LoginWrapper { - private static final Logger LOGGER = LogManager.getLogger(); - @ApiStatus.Internal - public static final ResourceLocation WRAPPER = new ResourceLocation("fml:loginwrapper"); - private EventNetworkChannel wrapperChannel; - - LoginWrapper() { - wrapperChannel = NetworkRegistry.ChannelBuilder.named(LoginWrapper.WRAPPER).clientAcceptedVersions(a -> true).serverAcceptedVersions(a -> true).networkProtocolVersion(() -> NetworkConstants.NETVERSION) - .eventNetworkChannel(); - wrapperChannel.addListener(this::wrapperReceived); - } - - private void wrapperReceived(final T packet) { - // we don't care about channel registration change events on this channel - if (packet instanceof NetworkEvent.ChannelRegistrationChangeEvent) return; - final NetworkEvent.Context wrappedContext = packet.getSource(); - final FriendlyByteBuf payload = packet.getPayload(); - ResourceLocation targetNetworkReceiver = NetworkConstants.FML_HANDSHAKE_RESOURCE; - FriendlyByteBuf data = null; - if (payload != null) { - targetNetworkReceiver = payload.readResourceLocation(); - final int payloadLength = payload.readVarInt(); - data = new FriendlyByteBuf(payload.readBytes(payloadLength)); - } - final int loginSequence = packet.getLoginIndex(); - LOGGER.debug(HandshakeHandler.FMLHSMARKER, "Recieved login wrapper packet event for channel {} with index {}", targetNetworkReceiver, loginSequence); - final NetworkEvent.Context context = new NetworkEvent.Context(wrappedContext.getNetworkManager(), wrappedContext.getDirection(), (rl, buf) -> { - LOGGER.debug(HandshakeHandler.FMLHSMARKER, "Dispatching wrapped packet reply for channel {} with index {}", rl, loginSequence); - wrappedContext.getPacketDispatcher().sendPacket(WRAPPER, this.wrapPacket(rl, buf)); - }); - final NetworkEvent.LoginPayloadEvent loginPayloadEvent = new NetworkEvent.LoginPayloadEvent(data, context, loginSequence); - NetworkRegistry.findTarget(targetNetworkReceiver).ifPresent(ni -> { - ni.dispatchLoginPacket(loginPayloadEvent); - wrappedContext.setPacketHandled(context.getPacketHandled()); - }); - } - - private FriendlyByteBuf wrapPacket(final ResourceLocation rl, final FriendlyByteBuf buf) { - FriendlyByteBuf pb = new FriendlyByteBuf(Unpooled.buffer(buf.capacity())); - pb.writeResourceLocation(rl); - pb.writeVarInt(buf.readableBytes()); - pb.writeBytes(buf); - return pb; - } - - void sendServerToClientLoginPacket(final ResourceLocation resourceLocation, final FriendlyByteBuf buffer, final int index, final Connection manager) { - FriendlyByteBuf pb = wrapPacket(resourceLocation, buffer); - manager.send(LoginNetworkDirection.LOGIN_TO_CLIENT.buildPacket(new INetworkDirection.PacketData(pb, index), WRAPPER)); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/MCRegisterPacketHandler.java b/src/main/java/net/neoforged/neoforge/network/MCRegisterPacketHandler.java deleted file mode 100644 index 2e657fb4cd..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/MCRegisterPacketHandler.java +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import com.mojang.logging.LogUtils; -import io.netty.buffer.Unpooled; -import io.netty.util.Attribute; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; -import net.minecraft.ResourceLocationException; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.resources.ResourceLocation; -import org.slf4j.Logger; - -public class MCRegisterPacketHandler { - private static final Logger LOGGER = LogUtils.getLogger(); - public static final MCRegisterPacketHandler INSTANCE = new MCRegisterPacketHandler(); - - public static class ChannelList { - private Set locations = new HashSet<>(); - private Set remoteLocations = Set.of(); - - public void updateFrom(final NetworkEvent.Context source, FriendlyByteBuf buffer, final NetworkEvent.RegistrationChangeType changeType) { - byte[] data = new byte[Math.max(buffer.readableBytes(), 0)]; - buffer.readBytes(data); - Set oldLocations = this.locations; - this.locations = bytesToResLocation(data); - this.remoteLocations = Set.copyOf(this.locations); - // ensure all locations receive updates, old and new. - oldLocations.addAll(this.locations); - oldLocations.stream() - .map(NetworkRegistry::findTarget) - .filter(Optional::isPresent) - .map(Optional::get) - .forEach(t -> t.dispatchEvent(new NetworkEvent.ChannelRegistrationChangeEvent(source, changeType))); - } - - byte[] toByteArray() { - ByteArrayOutputStream bos = new ByteArrayOutputStream(); - for (ResourceLocation rl : locations) { - try { - bos.write(rl.toString().getBytes(StandardCharsets.UTF_8)); - bos.write(0); - } catch (IOException e) { - // fake IOE - } - } - return bos.toByteArray(); - } - - private Set bytesToResLocation(byte[] all) { - HashSet rl = new HashSet<>(); - int last = 0; - for (int cur = 0; cur < all.length; cur++) { - if (all[cur] == '\0') { - String s = new String(all, last, cur - last, StandardCharsets.UTF_8); - try { - rl.add(new ResourceLocation(s)); - } catch (ResourceLocationException ex) { - LOGGER.warn("Invalid channel name received: {}. Ignoring", s); - } - last = cur + 1; - } - } - return rl; - } - - /** - * {@return the unmodifiable set of channel locations sent by the remote side} - * This is useful for interacting with other modloaders via the network to inspect registered network channel IDs. - */ - public Set getRemoteLocations() { - return this.remoteLocations; - } - } - - public void addChannels(Set locations, Connection manager) { - getFrom(manager).locations.addAll(locations); - } - - void registerListener(NetworkEvent evt) { - final ChannelList channelList = getFrom(evt); - channelList.updateFrom(evt.getSource(), evt.getPayload(), NetworkEvent.RegistrationChangeType.REGISTER); - evt.getSource().setPacketHandled(true); - } - - void unregisterListener(NetworkEvent evt) { - final ChannelList channelList = getFrom(evt); - channelList.updateFrom(evt.getSource(), evt.getPayload(), NetworkEvent.RegistrationChangeType.UNREGISTER); - evt.getSource().setPacketHandled(true); - } - - private static ChannelList getFrom(Connection manager) { - return fromAttr(manager.channel().attr(NetworkConstants.FML_MC_REGISTRY)); - } - - private static ChannelList getFrom(NetworkEvent event) { - return fromAttr(event.getSource().attr(NetworkConstants.FML_MC_REGISTRY)); - } - - private static ChannelList fromAttr(Attribute attr) { - attr.setIfAbsent(new ChannelList()); - return attr.get(); - } - - public void sendRegistry(Connection manager, final PlayNetworkDirection dir) { - FriendlyByteBuf pb = new FriendlyByteBuf(Unpooled.buffer()); - pb.writeBytes(getFrom(manager).toByteArray()); - final Packet iPacketICustomPacket = dir.buildPacket(new INetworkDirection.PacketData(pb, 0), NetworkConstants.MC_REGISTER_RESOURCE); - manager.send(iPacketICustomPacket); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkConstants.java b/src/main/java/net/neoforged/neoforge/network/NetworkConstants.java deleted file mode 100644 index 18eaf63b18..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/NetworkConstants.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import io.netty.util.AttributeKey; -import java.util.List; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.IExtensionPoint.DisplayTest; -import net.neoforged.neoforge.network.ConnectionData.ModMismatchData; -import net.neoforged.neoforge.network.HandshakeMessages.S2CModList; -import net.neoforged.neoforge.network.event.EventNetworkChannel; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -/** - * Constants related to networking - */ -public class NetworkConstants { - public static final String FMLNETMARKER = "FML"; - /** - * Netversion 3: {@link S2CModList} packet may include a list of non-vanilla synced datapack registry ids. - */ - public static final int FMLNETVERSION = 3; - public static final String NETVERSION = FMLNETMARKER + FMLNETVERSION; - public static final String NOVERSION = "NONE"; - - static final Marker NETWORK = MarkerManager.getMarker("FMLNETWORK"); - static final AttributeKey FML_NETVERSION = AttributeKey.valueOf("fml:netversion"); - static final AttributeKey FML_HANDSHAKE_HANDLER = AttributeKey.valueOf("fml:handshake"); - static final AttributeKey FML_MC_REGISTRY = AttributeKey.valueOf("minecraft:netregistry"); - static final AttributeKey FML_CONNECTION_DATA = AttributeKey.valueOf("fml:conndata"); - static final AttributeKey FML_MOD_MISMATCH_DATA = AttributeKey.valueOf("fml:mismatchdata"); - static final ResourceLocation FML_HANDSHAKE_RESOURCE = new ResourceLocation("fml:handshake"); - static final ResourceLocation FML_PLAY_RESOURCE = new ResourceLocation("fml:play"); - static final ResourceLocation MC_REGISTER_RESOURCE = new ResourceLocation("minecraft:register"); - static final ResourceLocation MC_UNREGISTER_RESOURCE = new ResourceLocation("minecraft:unregister"); - static final SimpleChannel handshakeChannel = NetworkInitialization.getHandshakeChannel(); - static final SimpleChannel playChannel = NetworkInitialization.getPlayChannel(); - static final List mcRegChannels = NetworkInitialization.buildMCRegistrationChannels(); - /** - * Return this value in your {@link DisplayTest} function to be ignored. - */ - public static final String IGNORESERVERONLY = DisplayTest.IGNORESERVERONLY; - - public static String init() { - return NetworkConstants.NETVERSION; - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkEvent.java b/src/main/java/net/neoforged/neoforge/network/NetworkEvent.java deleted file mode 100644 index 10aa2b2442..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/NetworkEvent.java +++ /dev/null @@ -1,289 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import io.netty.buffer.Unpooled; -import io.netty.util.Attribute; -import io.netty.util.AttributeKey; -import java.util.List; -import java.util.concurrent.CompletableFuture; -import java.util.function.BiConsumer; -import java.util.function.BiFunction; -import java.util.function.Consumer; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.PacketListener; -import net.minecraft.network.protocol.Packet; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.minecraft.util.thread.BlockableEventLoop; -import net.neoforged.bus.api.Event; -import net.neoforged.neoforge.common.util.LogicalSidedProvider; -import org.jetbrains.annotations.Nullable; - -public class NetworkEvent extends Event { - private final FriendlyByteBuf payload; - private final Context source; - private final int loginIndex; - - private NetworkEvent(final ICustomPacketPayloadWithBuffer payload, final Context source) { - this.payload = payload.buffer(); - this.source = source; - this.loginIndex = payload.packetIndex(); - } - - private NetworkEvent(final ICustomQueryPayloadWithBuffer payload, final Context source) { - this.payload = payload.buffer(); - this.source = source; - this.loginIndex = payload.packetIndex(); - } - - private NetworkEvent(final FriendlyByteBuf payload, final Context source, final int loginIndex) { - this.payload = payload; - this.source = source; - this.loginIndex = loginIndex; - } - - public NetworkEvent(final Context source) { - this.source = source; - this.payload = null; - this.loginIndex = -1; - } - - public FriendlyByteBuf getPayload() { - return payload; - } - - public Context getSource() { - return source; - } - - public int getLoginIndex() { - return loginIndex; - } - - public static class ServerCustomPayloadEvent extends NetworkEvent { - ServerCustomPayloadEvent(final ICustomPacketPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - - ServerCustomPayloadEvent(final ICustomQueryPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - } - - public static class ClientCustomPayloadEvent extends NetworkEvent { - ClientCustomPayloadEvent(final ICustomQueryPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - - ClientCustomPayloadEvent(final ICustomPacketPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - } - - public static class ServerCustomPayloadLoginEvent extends ServerCustomPayloadEvent { - ServerCustomPayloadLoginEvent(final ICustomPacketPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - - ServerCustomPayloadLoginEvent(final ICustomQueryPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - } - - public static class ClientCustomPayloadLoginEvent extends ClientCustomPayloadEvent { - ClientCustomPayloadLoginEvent(final ICustomPacketPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - - ClientCustomPayloadLoginEvent(final ICustomQueryPayloadWithBuffer payload, final Context source) { - super(payload, source); - } - } - - public static class GatherLoginPayloadsEvent extends Event { - private final List collected; - private final boolean isLocal; - - public GatherLoginPayloadsEvent(final List loginPayloadList, boolean isLocal) { - this.collected = loginPayloadList; - this.isLocal = isLocal; - } - - public void add(FriendlyByteBuf buffer, ResourceLocation channelName, String context) { - collected.add(new NetworkRegistry.LoginPayload(buffer, channelName, context)); - } - - public void add(FriendlyByteBuf buffer, ResourceLocation channelName, String context, boolean needsResponse) { - collected.add(new NetworkRegistry.LoginPayload(buffer, channelName, context, needsResponse)); - } - - public boolean isLocal() { - return isLocal; - } - } - - public static class LoginPayloadEvent extends NetworkEvent { - LoginPayloadEvent(final FriendlyByteBuf payload, final Context source, final int loginIndex) { - super(payload, source, loginIndex); - } - } - - public enum RegistrationChangeType { - REGISTER, UNREGISTER; - } - - /** - * Fired when the channel registration (see minecraft custom channel documentation) changes. Note the payload - * is not exposed. This fires to the resource location that owns the channel, when it's registration changes state. - * - * It seems plausible that this will fire multiple times for the same state, depending on what the server is doing. - * It just directly dispatches upon receipt. - */ - public static class ChannelRegistrationChangeEvent extends NetworkEvent { - private final RegistrationChangeType changeType; - - ChannelRegistrationChangeEvent(final Context source, RegistrationChangeType changeType) { - super(source); - this.changeType = changeType; - } - - public RegistrationChangeType getRegistrationChangeType() { - return this.changeType; - } - } - - /** - * Context for {@link NetworkEvent} - */ - public static class Context { - /** - * The {@link Connection} for this message. - */ - private final Connection networkManager; - - /** - * The {@link PlayNetworkDirection} this message has been received on. - */ - private final INetworkDirection networkDirection; - - /** - * The packet dispatcher for this event. Sends back to the origin. - */ - private final PacketDispatcher packetDispatcher; - private boolean packetHandled; - - Context(Connection netHandler, INetworkDirection networkDirection, int index) { - this(netHandler, networkDirection, new PacketDispatcher.NetworkManagerDispatcher(netHandler, index, ((INetworkDirection) networkDirection.reply())::buildPacket)); - } - - Context(Connection networkManager, INetworkDirection networkDirection, final BiConsumer packetSink) { - this(networkManager, networkDirection, new PacketDispatcher(packetSink)); - } - - Context(Connection networkManager, INetworkDirection networkDirection, PacketDispatcher dispatcher) { - this.networkManager = networkManager; - this.networkDirection = networkDirection; - this.packetDispatcher = dispatcher; - } - - public INetworkDirection getDirection() { - return networkDirection; - } - - public PacketDispatcher getPacketDispatcher() { - return packetDispatcher; - } - - public Attribute attr(AttributeKey key) { - return networkManager.channel().attr(key); - } - - public void setPacketHandled(boolean packetHandled) { - this.packetHandled = packetHandled; - } - - public boolean getPacketHandled() { - return packetHandled; - } - - public CompletableFuture enqueueWork(Runnable runnable) { - BlockableEventLoop executor = LogicalSidedProvider.WORKQUEUE.get(getDirection().getReceptionSide()); - // Must check ourselves as Minecraft will sometimes delay tasks even when they are received on the client thread - // Same logic as ThreadTaskExecutor#runImmediately without the join - if (!executor.isSameThread()) { - return executor.submitAsync(runnable); // Use the internal method so thread check isn't done twice - } else { - runnable.run(); - return CompletableFuture.completedFuture(null); - } - } - - /** - * When available, gets the sender for packets that are sent from a client to the server. - */ - @Nullable - public ServerPlayer getSender() { - PacketListener netHandler = networkManager.getPacketListener(); - if (netHandler instanceof ServerGamePacketListenerImpl) { - ServerGamePacketListenerImpl netHandlerPlayServer = (ServerGamePacketListenerImpl) netHandler; - return netHandlerPlayServer.player; - } - return null; - } - - public Connection getNetworkManager() { - return networkManager; - } - } - - /** - * Dispatcher for sending packets in response to a received packet. Abstracts out the difference between wrapped packets - * and unwrapped packets. - */ - public static class PacketDispatcher { - BiConsumer packetSink; - - PacketDispatcher(final BiConsumer packetSink) { - this.packetSink = packetSink; - } - - private PacketDispatcher() { - - } - - public void sendPacket(ResourceLocation resourceLocation, Consumer dataWriter) { - final FriendlyByteBuf buffer = new FriendlyByteBuf(Unpooled.buffer()); - dataWriter.accept(buffer); - sendPacket(resourceLocation, buffer); - } - - public void sendPacket(ResourceLocation resourceLocation, FriendlyByteBuf buffer) { - packetSink.accept(resourceLocation, buffer); - } - - static class NetworkManagerDispatcher extends PacketDispatcher { - private final Connection manager; - private final int packetIndex; - private final BiFunction> customPacketSupplier; - - NetworkManagerDispatcher(Connection manager, int packetIndex, BiFunction> customPacketSupplier) { - super(); - this.packetSink = this::dispatchPacket; - this.manager = manager; - this.packetIndex = packetIndex; - this.customPacketSupplier = customPacketSupplier; - } - - private void dispatchPacket(final ResourceLocation resourceLocation, final FriendlyByteBuf buffer) { - final Packet packet = this.customPacketSupplier.apply(new INetworkDirection.PacketData(buffer, packetIndex), resourceLocation); - this.manager.send(packet); - } - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkHooks.java b/src/main/java/net/neoforged/neoforge/network/NetworkHooks.java deleted file mode 100644 index 6f72ff7c01..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/NetworkHooks.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import io.netty.buffer.Unpooled; -import io.netty.channel.Channel; -import io.netty.channel.ChannelHandlerContext; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.Set; -import java.util.function.Consumer; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import net.minecraft.core.BlockPos; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.game.ClientGamePacketListener; -import net.minecraft.network.protocol.handshake.ClientIntentionPacket; -import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; -import net.minecraft.world.MenuProvider; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.inventory.MenuType; -import net.neoforged.fml.config.ConfigTracker; -import net.neoforged.fml.util.thread.EffectiveSide; -import net.neoforged.neoforge.client.ConfigScreenHandler; -import net.neoforged.neoforge.common.NeoForge; -import net.neoforged.neoforge.event.entity.player.PlayerContainerEvent; -import net.neoforged.neoforge.network.ConnectionData.ModMismatchData; -import net.neoforged.neoforge.network.filters.NetworkFilters; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.Nullable; - -public class NetworkHooks { - private static final Logger LOGGER = LogManager.getLogger(); - - public static String getFMLVersion(final String ip) { - return ip.contains("\0") ? Objects.equals(ip.split("\0")[1], NetworkConstants.NETVERSION) ? NetworkConstants.NETVERSION : ip.split("\0")[1] : NetworkConstants.NOVERSION; - } - - public static ConnectionType getConnectionType(final Supplier connection) { - return getConnectionType(connection.get().channel()); - } - - public static ConnectionType getConnectionType(ChannelHandlerContext context) { - return getConnectionType(context.channel()); - } - - private static ConnectionType getConnectionType(Channel channel) { - return ConnectionType.forVersionFlag(channel.attr(NetworkConstants.FML_NETVERSION).get()); - } - - @SuppressWarnings("unchecked") - public static Packet getEntitySpawningPacket(Entity entity) { - // ClientboundCustomPayloadPacket is an instance of Packet - return (Packet) NetworkConstants.playChannel.toVanillaPacket(new PlayMessages.SpawnEntity(entity), PlayNetworkDirection.PLAY_TO_CLIENT); - } - - public static boolean onCustomPayload(Packet packet, final ICustomPacketPayloadWithBuffer payload, final Connection manager) { - return NetworkRegistry.findTarget(payload.id()).filter(ni -> validateSideForProcessing(packet, payload, ni, manager)).map(ni -> ni.dispatch(payload.getDirection(packet), payload, manager)).orElse(Boolean.FALSE); - } - - public static boolean onCustomQuery(Packet packet, final ICustomQueryPayloadWithBuffer payload, final Connection manager) { - return NetworkRegistry.findTarget(payload.id()).filter(ni -> validateSideForProcessing(packet, payload, ni, manager)).map(ni -> ni.dispatch(payload.getDirection(packet), payload, manager)).orElse(Boolean.FALSE); - } - - private static boolean validateSideForProcessing(Packet packet, final ICustomPacketPayloadWithBuffer payload, final NetworkInstance ni, final Connection manager) { - if (payload.getDirection(packet).getReceptionSide() != EffectiveSide.get()) { - manager.disconnect(Component.literal("Illegal packet received, terminating connection")); - return false; - } - return true; - } - - private static boolean validateSideForProcessing(Packet packet, final ICustomQueryPayloadWithBuffer payload, final NetworkInstance ni, final Connection manager) { - if (payload.getDirection(packet).getReceptionSide() != EffectiveSide.get()) { - manager.disconnect(Component.literal("Illegal packet received, terminating connection")); - return false; - } - return true; - } - - public static void validatePacketDirection(final INetworkDirection packetDirection, final Optional> expectedDirection, final Connection connection) { - if (packetDirection != expectedDirection.orElse(packetDirection)) { - connection.disconnect(Component.literal("Illegal packet received, terminating connection")); - throw new IllegalStateException("Invalid packet received, aborting connection"); - } - } - - public static void registerServerLoginChannel(Connection manager, ClientIntentionPacket packet) { - manager.channel().attr(NetworkConstants.FML_NETVERSION).set(packet.getFMLVersion()); - HandshakeHandler.registerHandshake(manager, LoginNetworkDirection.LOGIN_TO_CLIENT); - } - - public synchronized static void registerClientLoginChannel(Connection manager) { - manager.channel().attr(NetworkConstants.FML_NETVERSION).set(NetworkConstants.NOVERSION); - HandshakeHandler.registerHandshake(manager, LoginNetworkDirection.LOGIN_TO_SERVER); - } - - public synchronized static void sendMCRegistryPackets(Connection manager, PlayNetworkDirection direction) { - NetworkFilters.injectIfNecessary(manager); - final Set resourceLocations = NetworkRegistry.buildChannelVersions().keySet().stream().filter(rl -> !Objects.equals(rl.getNamespace(), "minecraft")).collect(Collectors.toSet()); - MCRegisterPacketHandler.INSTANCE.addChannels(resourceLocations, manager); - MCRegisterPacketHandler.INSTANCE.sendRegistry(manager, direction); - } - - //TODO Dimensions.. -/* public synchronized static void sendDimensionDataPacket(NetworkManager manager, ServerPlayerEntity player) { -// don't send vanilla dims -if (player.dimension.isVanilla()) return; -// don't sent to local - we already have a valid dim registry locally -if (manager.isLocalChannel()) return; -FMLNetworkConstants.playChannel.sendTo(new FMLPlayMessages.DimensionInfoMessage(player.dimension), manager, NetworkDirection.PLAY_TO_CLIENT); -}*/ - - public static boolean isVanillaConnection(Connection manager) { - if (manager == null || manager.channel() == null) throw new NullPointerException("ARGH! Network Manager is null (" + manager != null ? "CHANNEL" : "MANAGER" + ")"); - return getConnectionType(() -> manager) == ConnectionType.VANILLA; - } - - public static void handleClientLoginSuccess(Connection manager) { - if (isVanillaConnection(manager)) { - LOGGER.info("Connected to a vanilla server. Catching up missing behaviour."); - ConfigTracker.INSTANCE.loadDefaultServerConfigs(); - } else { - LOGGER.info("Connected to a modded server."); - } - } - - public static boolean tickNegotiation(Connection networkManager) { - return HandshakeHandler.tickLogin(networkManager); - } - - /** - * Request to open a GUI on the client, from the server - * - * Refer to {@link ConfigScreenHandler.ConfigScreenFactory} for how to provide a function to consume - * these GUI requests on the client. - * - * @param player The player to open the GUI for - * @param containerSupplier A supplier of container properties including the registry name of the container - */ - public static void openScreen(ServerPlayer player, MenuProvider containerSupplier) { - openScreen(player, containerSupplier, buf -> {}); - } - - /** - * Request to open a GUI on the client, from the server - * - * Refer to {@link ConfigScreenHandler.ConfigScreenFactory} for how to provide a function to consume - * these GUI requests on the client. - * - * @param player The player to open the GUI for - * @param containerSupplier A supplier of container properties including the registry name of the container - * @param pos A block pos, which will be encoded into the auxillary data for this request - */ - public static void openScreen(ServerPlayer player, MenuProvider containerSupplier, BlockPos pos) { - openScreen(player, containerSupplier, buf -> buf.writeBlockPos(pos)); - } - - /** - * Request to open a GUI on the client, from the server - * - * Refer to {@link ConfigScreenHandler.ConfigScreenFactory} for how to provide a function to consume - * these GUI requests on the client. - * - * The maximum size for #extraDataWriter is 32600 bytes. - * - * @param player The player to open the GUI for - * @param containerSupplier A supplier of container properties including the registry name of the container - * @param extraDataWriter Consumer to write any additional data the GUI needs - */ - public static void openScreen(ServerPlayer player, MenuProvider containerSupplier, Consumer extraDataWriter) { - if (player.level().isClientSide) return; - player.doCloseContainer(); - player.nextContainerCounter(); - int openContainerId = player.containerCounter; - FriendlyByteBuf extraData = new FriendlyByteBuf(Unpooled.buffer()); - extraDataWriter.accept(extraData); - extraData.readerIndex(0); // reset to beginning in case modders read for whatever reason - - FriendlyByteBuf output = new FriendlyByteBuf(Unpooled.buffer()); - output.writeVarInt(extraData.readableBytes()); - output.writeBytes(extraData); - - if (output.readableBytes() > 32600 || output.readableBytes() < 1) { - throw new IllegalArgumentException("Invalid PacketBuffer for openGui, found " + output.readableBytes() + " bytes"); - } - var c = containerSupplier.createMenu(openContainerId, player.getInventory(), player); - if (c == null) - return; - MenuType type = c.getType(); - PlayMessages.OpenContainer msg = new PlayMessages.OpenContainer(type, openContainerId, containerSupplier.getDisplayName(), output); - NetworkConstants.playChannel.sendTo(msg, player.connection.connection, PlayNetworkDirection.PLAY_TO_CLIENT); - - player.containerMenu = c; - player.initMenu(player.containerMenu); - NeoForge.EVENT_BUS.post(new PlayerContainerEvent.Open(player, c)); - } - - /** - * Updates the current ConnectionData instance with new mod or channel data if the old instance did not have either of these yet, - * or creates a new ConnectionData instance with the new data if the current ConnectionData instance doesn't exist yet. - */ - static void appendConnectionData(Connection mgr, Map> modData, Map channels) { - ConnectionData oldData = mgr.channel().attr(NetworkConstants.FML_CONNECTION_DATA).get(); - - oldData = oldData != null ? new ConnectionData(oldData.getModData().isEmpty() ? modData : oldData.getModData(), oldData.getChannels().isEmpty() ? channels : oldData.getChannels()) : new ConnectionData(modData, channels); - mgr.channel().attr(NetworkConstants.FML_CONNECTION_DATA).set(oldData); - } - - @Nullable - public static ConnectionData getConnectionData(Connection mgr) { - return mgr.channel().attr(NetworkConstants.FML_CONNECTION_DATA).get(); - } - - @Nullable - public static ModMismatchData getModMismatchData(Connection mgr) { - return mgr.channel().attr(NetworkConstants.FML_MOD_MISMATCH_DATA).get(); - } - - @Nullable - public static MCRegisterPacketHandler.ChannelList getChannelList(Connection mgr) { - return mgr.channel().attr(NetworkConstants.FML_MC_REGISTRY).get(); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java index 4c53d8bb71..bd4655554b 100644 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java +++ b/src/main/java/net/neoforged/neoforge/network/NetworkInitialization.java @@ -5,50 +5,65 @@ package net.neoforged.neoforge.network; -import java.util.Arrays; -import java.util.List; -import net.neoforged.neoforge.network.event.EventNetworkChannel; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import net.neoforged.neoforge.registries.RegistryManager; - -class NetworkInitialization { - - public static SimpleChannel getHandshakeChannel() { - SimpleChannel handshakeChannel = NetworkRegistry.ChannelBuilder.named(NetworkConstants.FML_HANDSHAKE_RESOURCE).clientAcceptedVersions(a -> true).serverAcceptedVersions(a -> true).networkProtocolVersion(() -> NetworkConstants.NETVERSION).simpleChannel(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.C2SAcknowledge.class, 99, LoginNetworkDirection.LOGIN_TO_SERVER).decoder(HandshakeMessages.C2SAcknowledge::decode).consumerNetworkThread(HandshakeHandler.indexFirst(HandshakeHandler::handleClientAck)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.S2CModData.class, 5, LoginNetworkDirection.LOGIN_TO_CLIENT).decoder(HandshakeMessages.S2CModData::decode).markAsLoginPacket().noResponse().consumerNetworkThread(HandshakeHandler.consumerFor(HandshakeHandler::handleModData)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.S2CModList.class, 1, LoginNetworkDirection.LOGIN_TO_CLIENT).decoder(HandshakeMessages.S2CModList::decode).markAsLoginPacket().consumerNetworkThread(HandshakeHandler.consumerFor(HandshakeHandler::handleServerModListOnClient)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.C2SModListReply.class, 2, LoginNetworkDirection.LOGIN_TO_SERVER).decoder(HandshakeMessages.C2SModListReply::decode).consumerNetworkThread(HandshakeHandler.indexFirst(HandshakeHandler::handleClientModListOnServer)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.S2CRegistry.class, 3, LoginNetworkDirection.LOGIN_TO_CLIENT).decoder(HandshakeMessages.S2CRegistry::decode).buildLoginPacketList(RegistryManager::generateRegistryPackets). //TODO: Make this non-static, and store a cache on the client. - consumerNetworkThread(HandshakeHandler.consumerFor(HandshakeHandler::handleRegistryMessage)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.S2CConfigData.class, 4, LoginNetworkDirection.LOGIN_TO_CLIENT).decoder(HandshakeMessages.S2CConfigData::decode).buildLoginPacketList(ConfigSync.INSTANCE::syncConfigs).consumerNetworkThread(HandshakeHandler.consumerFor(HandshakeHandler::handleConfigSync)).add(); - - handshakeChannel.simpleLoginMessageBuilder(HandshakeMessages.S2CChannelMismatchData.class, 6, LoginNetworkDirection.LOGIN_TO_CLIENT).decoder(HandshakeMessages.S2CChannelMismatchData::decode).consumerNetworkThread(HandshakeHandler.consumerFor(HandshakeHandler::handleModMismatchData)).add(); - - return handshakeChannel; - } - - public static SimpleChannel getPlayChannel() { - SimpleChannel playChannel = NetworkRegistry.ChannelBuilder.named(NetworkConstants.FML_PLAY_RESOURCE).clientAcceptedVersions(a -> true).serverAcceptedVersions(a -> true).networkProtocolVersion(() -> NetworkConstants.NETVERSION).simpleChannel(); - - playChannel.messageBuilder(PlayMessages.SpawnEntity.class, 0).decoder(PlayMessages.SpawnEntity::decode).encoder(PlayMessages.SpawnEntity::encode).consumerMainThread(PlayMessages.SpawnEntity::handle).add(); - - playChannel.messageBuilder(PlayMessages.OpenContainer.class, 1).decoder(PlayMessages.OpenContainer::decode).encoder(PlayMessages.OpenContainer::encode).consumerMainThread(PlayMessages.OpenContainer::handle).add(); - - return playChannel; - } - - public static List buildMCRegistrationChannels() { - final EventNetworkChannel mcRegChannel = NetworkRegistry.ChannelBuilder.named(NetworkConstants.MC_REGISTER_RESOURCE).clientAcceptedVersions(a -> true).serverAcceptedVersions(a -> true).networkProtocolVersion(() -> NetworkConstants.NETVERSION).eventNetworkChannel(); - mcRegChannel.addListener(MCRegisterPacketHandler.INSTANCE::registerListener); - final EventNetworkChannel mcUnregChannel = NetworkRegistry.ChannelBuilder.named(NetworkConstants.MC_UNREGISTER_RESOURCE).clientAcceptedVersions(a -> true).serverAcceptedVersions(a -> true).networkProtocolVersion(() -> NetworkConstants.NETVERSION).eventNetworkChannel(); - mcUnregChannel.addListener(MCRegisterPacketHandler.INSTANCE::unregisterListener); - return Arrays.asList(mcRegChannel, mcUnregChannel); +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.handlers.ClientPayloadHandler; +import net.neoforged.neoforge.network.handlers.ServerPayloadHandler; +import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload; +import net.neoforged.neoforge.network.payload.AdvancedOpenScreenPayload; +import net.neoforged.neoforge.network.payload.ConfigFilePayload; +import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; +import net.neoforged.neoforge.network.payload.TierSortingRegistryPayload; +import net.neoforged.neoforge.network.payload.TierSortingRegistrySyncCompletePayload; +import net.neoforged.neoforge.network.registration.IPayloadRegistrar; +import org.jetbrains.annotations.ApiStatus; + +@Mod.EventBusSubscriber(modid = NeoForgeVersion.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +@ApiStatus.Internal +public class NetworkInitialization { + + @SubscribeEvent + private static void register(final RegisterPayloadHandlerEvent event) { + final IPayloadRegistrar registrar = event.registrar(NeoForgeVersion.MOD_ID) + .versioned(NeoForgeVersion.getSpec()) + .optional(); + registrar + .configuration( + FrozenRegistrySyncStartPayload.ID, + FrozenRegistrySyncStartPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)) + .configuration( + FrozenRegistryPayload.ID, + FrozenRegistryPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)) + .configuration( + FrozenRegistrySyncCompletedPayload.ID, + FrozenRegistrySyncCompletedPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle) + .server(ServerPayloadHandler.getInstance()::handle)) + .configuration( + TierSortingRegistryPayload.ID, + TierSortingRegistryPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)) + .configuration( + TierSortingRegistrySyncCompletePayload.ID, + TierSortingRegistrySyncCompletePayload::new, + handlers -> handlers.server(ServerPayloadHandler.getInstance()::handle)) + .configuration( + ConfigFilePayload.ID, + ConfigFilePayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)) + .play( + AdvancedAddEntityPayload.ID, + AdvancedAddEntityPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)) + .play( + AdvancedOpenScreenPayload.ID, + AdvancedOpenScreenPayload::new, + handlers -> handlers.client(ClientPayloadHandler.getInstance()::handle)); } } diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkInstance.java b/src/main/java/net/neoforged/neoforge/network/NetworkInstance.java deleted file mode 100644 index 13791b37b9..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/NetworkInstance.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import java.util.List; -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; -import net.minecraft.network.Connection; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.bus.api.BusBuilder; -import net.neoforged.bus.api.Event; -import net.neoforged.bus.api.EventListener; -import net.neoforged.bus.api.IEventBus; - -public class NetworkInstance { - public ResourceLocation getChannelName() { - return channelName; - } - - private final ResourceLocation channelName; - private final String networkProtocolVersion; - private final Predicate clientAcceptedVersions; - private final Predicate serverAcceptedVersions; - private final IEventBus networkEventBus; - - NetworkInstance(ResourceLocation channelName, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { - this.channelName = channelName; - this.networkProtocolVersion = networkProtocolVersion.get(); - this.clientAcceptedVersions = clientAcceptedVersions; - this.serverAcceptedVersions = serverAcceptedVersions; - this.networkEventBus = BusBuilder.builder().setExceptionHandler(this::handleError).build(); - } - - private void handleError(IEventBus iEventBus, Event event, EventListener[] iEventListeners, int i, Throwable throwable) { - - } - - public void addListener(Consumer eventListener) { - this.networkEventBus.addListener(eventListener); - } - - public void addGatherListener(Consumer eventListener) { - this.networkEventBus.addListener(eventListener); - } - - public void registerObject(final Object object) { - this.networkEventBus.register(object); - } - - public void unregisterObject(final Object object) { - this.networkEventBus.unregister(object); - } - - boolean dispatch(final PlayNetworkDirection side, final ICustomPacketPayloadWithBuffer packet, final Connection manager) { - final NetworkEvent.Context context = new NetworkEvent.Context(manager, side, packet.packetIndex()); - this.networkEventBus.post(side.getEvent(packet, context)); - return context.getPacketHandled(); - } - - boolean dispatch(final LoginNetworkDirection side, final ICustomQueryPayloadWithBuffer packet, final Connection manager) { - final NetworkEvent.Context context = new NetworkEvent.Context(manager, side, packet.packetIndex()); - this.networkEventBus.post(side.getEvent(packet, context)); - return context.getPacketHandled(); - } - - String getNetworkProtocolVersion() { - return networkProtocolVersion; - } - - boolean tryServerVersionOnClient(final String serverVersion) { - return this.clientAcceptedVersions.test(serverVersion); - } - - boolean tryClientVersionOnServer(final String clientVersion) { - return this.serverAcceptedVersions.test(clientVersion); - } - - void dispatchGatherLogin(final List loginPayloadList, boolean isLocal) { - this.networkEventBus.post(new NetworkEvent.GatherLoginPayloadsEvent(loginPayloadList, isLocal)); - } - - void dispatchLoginPacket(final NetworkEvent.LoginPayloadEvent loginPayloadEvent) { - this.networkEventBus.post(loginPayloadEvent); - } - - void dispatchEvent(final NetworkEvent networkEvent) { - this.networkEventBus.post(networkEvent); - } - - public boolean isRemotePresent(Connection manager) { - ConnectionData connectionData = NetworkHooks.getConnectionData(manager); - MCRegisterPacketHandler.ChannelList channelList = NetworkHooks.getChannelList(manager); - return (connectionData != null && connectionData.getChannels().containsKey(channelName)) - // if it's not in the fml connection data, let's check if it's sent by another modloader. - || (channelList != null && channelList.getRemoteLocations().contains(channelName)); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/NetworkRegistry.java b/src/main/java/net/neoforged/neoforge/network/NetworkRegistry.java deleted file mode 100644 index f6f652ecb7..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/NetworkRegistry.java +++ /dev/null @@ -1,424 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.function.BiFunction; -import java.util.function.Predicate; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.event.EventNetworkChannel; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import net.neoforged.neoforge.registries.DataPackRegistriesHooks; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -/** - * The impl registry. Tracks channels on behalf of mods. - */ -public class NetworkRegistry { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY"); - - private static Map instances = Collections.synchronizedMap(new HashMap<>()); - - /** - * Special value for clientAcceptedVersions and serverAcceptedVersions predicates indicating the other side lacks - * this channel. - */ - public static ServerStatusPing.ChannelData ABSENT = new ServerStatusPing.ChannelData(new ResourceLocation("absent"), "ABSENT \uD83E\uDD14", false); - - public static String ACCEPTVANILLA = new String("ALLOWVANILLA \uD83D\uDC93\uD83D\uDC93\uD83D\uDC93"); - - /** - * Makes a version predicate that accepts connections to vanilla or without the channel. - * - * @param protocolVersion The protocol version, which will be matched exactly. - * @return A new predicate with the new conditions. - */ - public static Predicate acceptMissingOr(final String protocolVersion) { - return acceptMissingOr(protocolVersion::equals); - } - - /** - * Makes a version predicate that accepts connections to vanilla or without the channel. - * - * @param versionCheck The main version predicate, which should check the version number of the protocol. - * @return A new predicate with the new conditions. - */ - public static Predicate acceptMissingOr(Predicate versionCheck) { - return versionCheck.or(ABSENT.version()::equals).or(ACCEPTVANILLA::equals); - } - - public static List getServerNonVanillaNetworkMods() { - return listRejectedVanillaMods(NetworkInstance::tryClientVersionOnServer); - } - - public static List getClientNonVanillaNetworkMods() { - return listRejectedVanillaMods(NetworkInstance::tryServerVersionOnClient); - } - - public static boolean acceptsVanillaClientConnections() { - return (instances.isEmpty() || getServerNonVanillaNetworkMods().isEmpty()) && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty(); - } - - public static boolean canConnectToVanillaServer() { - return instances.isEmpty() || getClientNonVanillaNetworkMods().isEmpty(); - } - - /** - * Create a new {@link SimpleChannel}. - * - * @param name The registry name for this channel. Must be unique - * @param networkProtocolVersion The impl protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} - * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} - * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} - * @return A new {@link SimpleChannel} - * - * @see ChannelBuilder#newSimpleChannel(ResourceLocation, Supplier, Predicate, Predicate) - */ - public static SimpleChannel newSimpleChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { - return new SimpleChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); - } - - /** - * Create a new {@link EventNetworkChannel}. - * - * @param name The registry name for this channel. Must be unique - * @param networkProtocolVersion The impl protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} - * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} - * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} - * - * @return A new {@link EventNetworkChannel} - * - * @see ChannelBuilder#newEventChannel(ResourceLocation, Supplier, Predicate, Predicate) - */ - public static EventNetworkChannel newEventChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { - return new EventNetworkChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); - } - - /** - * Creates the internal {@link NetworkInstance} that tracks the channel data. - * - * @param name registry name - * @param networkProtocolVersion The protocol version string - * @param clientAcceptedVersions The client accepted predicate - * @param serverAcceptedVersions The server accepted predicate - * @return The {@link NetworkInstance} - * @throws IllegalArgumentException if the name already exists - */ - private static NetworkInstance createInstance(ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { - if (lock) { - LOGGER.error(NETREGISTRY, "Attempted to register channel {} even though registry phase is over", name); - throw new IllegalArgumentException("Registration of impl channels is locked"); - } - if (instances.containsKey(name)) { - LOGGER.error(NETREGISTRY, "NetworkDirection channel {} already registered.", name); - throw new IllegalArgumentException("NetworkDirection Channel {" + name + "} already registered"); - } - final NetworkInstance networkInstance = new NetworkInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); - instances.put(name, networkInstance); - return networkInstance; - } - - /** - * Find the {@link NetworkInstance}, if possible - * - * @param resourceLocation The impl instance to lookup - * @return The {@link Optional} {@link NetworkInstance} - */ - static Optional findTarget(ResourceLocation resourceLocation) { - return Optional.ofNullable(instances.get(resourceLocation)); - } - - /** - * Construct the Map representation of the channel list, for use during login handshaking - * - * @see HandshakeMessages.S2CModList - * @see HandshakeMessages.C2SModListReply - */ - static Map buildChannelVersions() { - return instances.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().getNetworkProtocolVersion())); - } - - /** - * Construct the Map representation of the channel list, for the client to check against during list ping - * - * @see HandshakeMessages.S2CModList - * @see HandshakeMessages.C2SModListReply - */ - static Map buildChannelVersionsForListPing() { - return instances.entrySet().stream().filter(p -> !p.getKey().getNamespace().equals("fml")).collect(Collectors.toMap(Map.Entry::getKey, val -> new ServerStatusPing.ChannelData(val.getKey(), val.getValue().getNetworkProtocolVersion(), val.getValue().tryClientVersionOnServer(ABSENT.version())))); - } - - static List listRejectedVanillaMods(BiFunction testFunction) { - final List> results = instances.values().stream().map(ni -> { - final String incomingVersion = ACCEPTVANILLA; - final boolean test = testFunction.apply(ni, incomingVersion); - LOGGER.debug(NETREGISTRY, "Channel '{}' : Vanilla acceptance test: {}", ni.getChannelName(), test ? "ACCEPTED" : "REJECTED"); - return Pair.of(ni.getChannelName(), test); - }).filter(p -> !p.getRight()).toList(); - - if (!results.isEmpty()) { - LOGGER.error(NETREGISTRY, "Channels [{}] rejected vanilla connections", - results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(","))); - return results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.toList()); - } - LOGGER.debug(NETREGISTRY, "Accepting channel list from vanilla"); - return Collections.emptyList(); - } - - /** - * Validate the channels from the server on the client. Tests the client predicates against the server - * supplied impl protocol version. - * - * @param channels An @{@link Map} of name->version pairs for testing - * @return a map of mismatched channel ids and versions, or an empty map if all channels accept themselves - */ - static Map validateClientChannels(final Map channels) { - return validateChannels(channels, "server", NetworkInstance::tryServerVersionOnClient); - } - - /** - * Validate the channels from the client on the server. Tests the server predicates against the client - * supplied impl protocol version. - * - * @param channels An @{@link Map} of name->version pairs for testing - * @return a map of mismatched channel ids and versions, or an empty map if all channels accept themselves - */ - static Map validateServerChannels(final Map channels) { - return validateChannels(channels, "client", NetworkInstance::tryClientVersionOnServer); - } - - /** - * Tests if the map matches with the supplied predicate tester - * - * @param incoming An @{@link Map} of name->version pairs for testing - * @param originName A label for use in logging (where the version pairs came from) - * @param testFunction The test function to use for testing - * @return a map of mismatched channel ids and versions, or an empty map if all channels accept themselves - */ - private static Map validateChannels(final Map incoming, final String originName, BiFunction testFunction) { - final Map results = instances.values().stream().map(ni -> { - final String incomingVersion = incoming.getOrDefault(ni.getChannelName(), ABSENT.version()); - final boolean test = testFunction.apply(ni, incomingVersion); - LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' from {} : {}", ni.getChannelName(), incomingVersion, originName, test ? "ACCEPTED" : "REJECTED"); - return Pair.of(Pair.of(ni.getChannelName(), incomingVersion), test); - }).filter(p -> !p.getRight()).map(Pair::getLeft).collect(Collectors.toMap(Pair::getLeft, Pair::getRight)); - - if (!results.isEmpty()) { - LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number", - results.keySet().stream().map(Object::toString).collect(Collectors.joining(",")), - originName); - return results; - } - LOGGER.debug(NETREGISTRY, "Accepting channel list from {}", originName); - return results; - } - - /** - * Retrieve the {@link LoginPayload} list for dispatch during {@link HandshakeHandler#tickLogin(Connection)} handling. - * Dispatches {@link NetworkEvent.GatherLoginPayloadsEvent} to each {@link NetworkInstance}. - * - * @return The {@link LoginPayload} list - * @param direction the impl direction for the request - only gathers for LOGIN_TO_CLIENT - */ - static List gatherLoginPayloads(final LoginNetworkDirection direction, boolean isLocal) { - if (direction != LoginNetworkDirection.LOGIN_TO_CLIENT) return Collections.emptyList(); - List gatheredPayloads = new ArrayList<>(); - instances.values().forEach(ni -> ni.dispatchGatherLogin(gatheredPayloads, isLocal)); - return gatheredPayloads; - } - - public static boolean checkListPingCompatibilityForClient(Map incoming) { - Set handled = new HashSet<>(); - final List> results = instances.values().stream().filter(p -> !p.getChannelName().getNamespace().equals("fml")).map(ni -> { - final ServerStatusPing.ChannelData incomingVersion = incoming.getOrDefault(ni.getChannelName(), ABSENT); - final boolean test = ni.tryServerVersionOnClient(incomingVersion.version()); - handled.add(ni.getChannelName()); - LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' during listping : {}", ni.getChannelName(), incomingVersion, test ? "ACCEPTED" : "REJECTED"); - return Pair.of(ni.getChannelName(), test); - }).filter(p -> !p.getRight()).collect(Collectors.toList()); - final List missingButRequired = incoming.entrySet().stream().filter(p -> !p.getKey().getNamespace().equals("fml")).filter(p -> !p.getValue().required()).filter(p -> !handled.contains(p.getKey())).map(Map.Entry::getKey).collect(Collectors.toList()); - - if (!results.isEmpty()) { - LOGGER.error(NETREGISTRY, "Channels [{}] rejected their server side version number during listping", - results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(","))); - return false; - } - if (!missingButRequired.isEmpty()) { - LOGGER.error(NETREGISTRY, "The server is likely to require channel [{}] to be present, yet we don't have it", - missingButRequired); - return false; - } - LOGGER.debug(NETREGISTRY, "Accepting channel list during listping"); - return true; - } - - private static boolean lock = false; - - public boolean isLocked() { - return lock; - } - - public static void lock() { - lock = true; - } - - /** - * Tracks individual outbound messages for dispatch to clients during login handling. Gathered by dispatching - * {@link NetworkEvent.GatherLoginPayloadsEvent} during early connection handling. - */ - public static class LoginPayload { - /** - * The data for sending - */ - private final FriendlyByteBuf data; - /** - * A channel which will receive a {@link NetworkEvent.LoginPayloadEvent} from the {@link LoginWrapper} - */ - private final ResourceLocation channelName; - - /** - * Some context for logging purposes - */ - private final String messageContext; - - /** - * If the connection should await a response to this packet to continue with the handshake - */ - private final boolean needsResponse; - - public LoginPayload(final FriendlyByteBuf buffer, final ResourceLocation channelName, final String messageContext) { - this(buffer, channelName, messageContext, true); - } - - public LoginPayload(final FriendlyByteBuf buffer, final ResourceLocation channelName, final String messageContext, final boolean needsResponse) { - this.data = buffer; - this.channelName = channelName; - this.messageContext = messageContext; - this.needsResponse = needsResponse; - } - - public FriendlyByteBuf getData() { - return data; - } - - public ResourceLocation getChannelName() { - return channelName; - } - - public String getMessageContext() { - return messageContext; - } - - public boolean needsResponse() { - return needsResponse; - } - } - - /** - * Builder for constructing impl channels using a builder style API. - */ - public static class ChannelBuilder { - private ResourceLocation channelName; - private Supplier networkProtocolVersion; - private Predicate clientAcceptedVersions; - private Predicate serverAcceptedVersions; - - /** - * The name of the channel. Must be unique. - * - * @param channelName The name of the channel - * @return the channel builder - */ - public static ChannelBuilder named(ResourceLocation channelName) { - ChannelBuilder builder = new ChannelBuilder(); - builder.channelName = channelName; - return builder; - } - - /** - * The impl protocol string for this channel. This will be gathered during login and sent to - * the remote partner, where it will be tested with against the relevant predicate. - * - * @see #serverAcceptedVersions(Predicate) - * @see #clientAcceptedVersions(Predicate) - * @param networkProtocolVersion A supplier of strings for impl protocol version testing - * @return the channel builder - */ - public ChannelBuilder networkProtocolVersion(Supplier networkProtocolVersion) { - this.networkProtocolVersion = networkProtocolVersion; - return this; - } - - /** - * A predicate run on the client, with the {@link #networkProtocolVersion(Supplier)} string from - * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of - * the channel on the remote side. - * - * @param clientAcceptedVersions A predicate for testing - * @return the channel builder - */ - public ChannelBuilder clientAcceptedVersions(Predicate clientAcceptedVersions) { - this.clientAcceptedVersions = clientAcceptedVersions; - return this; - } - - /** - * A predicate run on the server, with the {@link #networkProtocolVersion(Supplier)} string from - * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of - * the channel on the remote side. - * - * @param serverAcceptedVersions A predicate for testing - * @return the channel builder - */ - public ChannelBuilder serverAcceptedVersions(Predicate serverAcceptedVersions) { - this.serverAcceptedVersions = serverAcceptedVersions; - return this; - } - - /** - * Create the impl instance - * - * @return the {@link NetworkInstance} - */ - private NetworkInstance createNetworkInstance() { - return createInstance(channelName, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); - } - - /** - * Build a new {@link SimpleChannel} with this builder's configuration. - * - * @return A new {@link SimpleChannel} - */ - public SimpleChannel simpleChannel() { - return new SimpleChannel(createNetworkInstance()); - } - - /** - * Build a new {@link EventNetworkChannel} with this builder's configuration. - * - * @return A new {@link EventNetworkChannel} - */ - public EventNetworkChannel eventNetworkChannel() { - return new EventNetworkChannel(createNetworkInstance()); - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java index 4cfe1196e3..1f1fefc37f 100644 --- a/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java +++ b/src/main/java/net/neoforged/neoforge/network/PacketDistributor.java @@ -5,13 +5,21 @@ package net.neoforged.neoforge.network; +import java.util.ArrayList; import java.util.List; +import java.util.Objects; import java.util.function.BiFunction; import java.util.function.Consumer; +import java.util.function.Function; import java.util.function.Supplier; import net.minecraft.client.Minecraft; -import net.minecraft.network.Connection; import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; import net.minecraft.resources.ResourceKey; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ServerChunkCache; @@ -19,71 +27,60 @@ import net.minecraft.world.entity.Entity; import net.minecraft.world.level.Level; import net.minecraft.world.level.chunk.LevelChunk; -import net.neoforged.neoforge.network.simple.SimpleChannel; import net.neoforged.neoforge.server.ServerLifecycleHooks; /** * Means to distribute packets in various ways - * - * @see SimpleChannel#send(PacketTarget, Object) - * - * @param */ public class PacketDistributor { /** - * Send to the player specified in the Supplier + * Send to the player specified *
- * {@link #with(Supplier)} Player + * {@link #with(Object)} Player */ - public static final PacketDistributor PLAYER = new PacketDistributor<>(PacketDistributor::playerConsumer, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor PLAYER = new PacketDistributor<>(PacketDistributor::playerConsumer, PacketFlow.CLIENTBOUND); /** - * Send to everyone in the dimension specified in the Supplier + * Send to everyone in the dimension specified *
- * {@link #with(Supplier)} DimensionType + * {@link #with(Object)} DimensionType */ - public static final PacketDistributor> DIMENSION = new PacketDistributor<>(PacketDistributor::playerListDimConsumer, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor> DIMENSION = new PacketDistributor<>(PacketDistributor::playerListDimConsumer, PacketFlow.CLIENTBOUND); /** - * Send to everyone near the {@link TargetPoint} specified in the Supplier + * Send to everyone near the {@link TargetPoint} specified *
- * {@link #with(Supplier)} TargetPoint + * {@link #with(Object)} TargetPoint */ - public static final PacketDistributor NEAR = new PacketDistributor<>(PacketDistributor::playerListPointConsumer, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor NEAR = new PacketDistributor<>(PacketDistributor::playerListPointConsumer, PacketFlow.CLIENTBOUND); /** * Send to everyone *
* {@link #noArg()} */ - public static final PacketDistributor ALL = new PacketDistributor<>(PacketDistributor::playerListAll, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor ALL = new PacketDistributor<>(PacketDistributor::playerListAll, PacketFlow.CLIENTBOUND); /** * Send to the server (CLIENT to SERVER) *
* {@link #noArg()} */ - public static final PacketDistributor SERVER = new PacketDistributor<>(PacketDistributor::clientToServer, PlayNetworkDirection.PLAY_TO_SERVER); + public static final PacketDistributor SERVER = new PacketDistributor<>(PacketDistributor::clientToServer, PacketFlow.SERVERBOUND); /** - * Send to all tracking the Entity in the Supplier + * Send to all tracking the Entity *
- * {@link #with(Supplier)} Entity + * {@link #with(Object)} Entity */ - public static final PacketDistributor TRACKING_ENTITY = new PacketDistributor<>(PacketDistributor::trackingEntity, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor TRACKING_ENTITY = new PacketDistributor<>(PacketDistributor::trackingEntity, PacketFlow.CLIENTBOUND); /** - * Send to all tracking the Entity and Player in the Supplier + * Send to all tracking the Entity and Player *
- * {@link #with(Supplier)} Entity + * {@link #with(Object)} Entity */ - public static final PacketDistributor TRACKING_ENTITY_AND_SELF = new PacketDistributor<>(PacketDistributor::trackingEntityAndSelf, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor TRACKING_ENTITY_AND_SELF = new PacketDistributor<>(PacketDistributor::trackingEntityAndSelf, PacketFlow.CLIENTBOUND); /** - * Send to all tracking the Chunk in the Supplier + * Send to all tracking the Chunk *
- * {@link #with(Supplier)} Chunk + * {@link #with(Object)} Chunk */ - public static final PacketDistributor TRACKING_CHUNK = new PacketDistributor<>(PacketDistributor::trackingChunk, PlayNetworkDirection.PLAY_TO_CLIENT); - /** - * Send to the supplied list of NetworkManager instances in the Supplier - *
- * {@link #with(Supplier)} List of NetworkManager - */ - public static final PacketDistributor> NMLIST = new PacketDistributor<>(PacketDistributor::networkManagerList, PlayNetworkDirection.PLAY_TO_CLIENT); + public static final PacketDistributor TRACKING_CHUNK = new PacketDistributor<>(PacketDistributor::trackingChunk, PacketFlow.CLIENTBOUND); public static final class TargetPoint { @@ -151,8 +148,6 @@ public static Supplier p(double x, double y, double z, double r2, R /** * A Distributor curried with a specific value instance, for actual dispatch * - * @see SimpleChannel#send(PacketTarget, Object) - * */ public static class PacketTarget { private final Consumer> packetConsumer; @@ -167,18 +162,40 @@ public void send(Packet packet) { packetConsumer.accept(packet); } - public PlayNetworkDirection getDirection() { - return distributor.direction; + public void send(CustomPacketPayload... payloads) { + if (flow().isClientbound()) { + if (payloads.length > 1) { + final List> packets = new ArrayList<>(); + for (CustomPacketPayload payload : payloads) { + packets.add(new ClientboundCustomPayloadPacket(payload)); + } + this.send(new ClientboundBundlePacket(packets)); + } else if (payloads.length == 1) { + this.send(new ClientboundCustomPayloadPacket(payloads[0])); + } + } else { + for (CustomPacketPayload payload : payloads) { + this.send(new ServerboundCustomPayloadPacket(payload)); + } + } + } + + public PacketFlow flow() { + return distributor.flow; } } - private final BiFunction, Supplier, Consumer>> functor; - private final PlayNetworkDirection direction; + private final BiFunction, T, Consumer>> functor; + private final PacketFlow flow; - public PacketDistributor(BiFunction, Supplier, Consumer>> functor, PlayNetworkDirection direction) { + public PacketDistributor(BiFunction, T, Consumer>> functor, PacketFlow flow) { this.functor = functor; - this.direction = direction; + this.flow = flow; + } + + public PacketDistributor(Function, Consumer>> functor, PacketFlow flow) { + this((d, t) -> functor.apply(d), flow); } /** @@ -187,7 +204,7 @@ public PacketDistributor(BiFunction, Supplier, Consumer< * @param input The input to apply * @return A curried instance */ - public PacketTarget with(Supplier input) { + public PacketTarget with(T input) { return new PacketTarget(functor.apply(this, input), this); } @@ -199,57 +216,50 @@ public PacketTarget with(Supplier input) { * @return A curried instance */ public PacketTarget noArg() { - return new PacketTarget(functor.apply(this, () -> null), this); + return new PacketTarget(functor.apply(this, null), this); } - private Consumer> playerConsumer(final Supplier entityPlayerMPSupplier) { - return p -> entityPlayerMPSupplier.get().connection.connection.send(p); + private Consumer> playerConsumer(final ServerPlayer entityPlayerMP) { + return p -> entityPlayerMP.connection.send(p); } - private Consumer> playerListDimConsumer(final Supplier> dimensionTypeSupplier) { - return p -> getServer().getPlayerList().broadcastAll(p, dimensionTypeSupplier.get()); + private Consumer> playerListDimConsumer(final ResourceKey dimensionType) { + return p -> getServer().getPlayerList().broadcastAll(p, dimensionType); } - private Consumer> playerListAll(final Supplier voidSupplier) { + private Consumer> playerListAll() { return p -> getServer().getPlayerList().broadcastAll(p); } - private Consumer> clientToServer(final Supplier voidSupplier) { - return p -> Minecraft.getInstance().getConnection().send(p); + private Consumer> clientToServer() { + return p -> Objects.requireNonNull(Minecraft.getInstance().getConnection()).send(p); } - private Consumer> playerListPointConsumer(final Supplier targetPointSupplier) { + private Consumer> playerListPointConsumer(final TargetPoint targetPoint) { return p -> { - final TargetPoint tp = targetPointSupplier.get(); + final TargetPoint tp = targetPoint; getServer().getPlayerList().broadcast(tp.excluded, tp.x, tp.y, tp.z, tp.r2, tp.dim, p); }; } - private Consumer> trackingEntity(final Supplier entitySupplier) { + private Consumer> trackingEntity(final Entity entity) { return p -> { - final Entity entity = entitySupplier.get(); ((ServerChunkCache) entity.getCommandSenderWorld().getChunkSource()).broadcast(entity, p); }; } - private Consumer> trackingEntityAndSelf(final Supplier entitySupplier) { + private Consumer> trackingEntityAndSelf(final Entity entity) { return p -> { - final Entity entity = entitySupplier.get(); ((ServerChunkCache) entity.getCommandSenderWorld().getChunkSource()).broadcastAndSend(entity, p); }; } - private Consumer> trackingChunk(final Supplier chunkPosSupplier) { + private Consumer> trackingChunk(final LevelChunk chunkPos) { return p -> { - final LevelChunk chunk = chunkPosSupplier.get(); - ((ServerChunkCache) chunk.getLevel().getChunkSource()).chunkMap.getPlayers(chunk.getPos(), false).forEach(e -> e.connection.send(p)); + ((ServerChunkCache) chunkPos.getLevel().getChunkSource()).chunkMap.getPlayers(chunkPos.getPos(), false).forEach(e -> e.connection.send(p)); }; } - private Consumer> networkManagerList(final Supplier> nmListSupplier) { - return p -> nmListSupplier.get().forEach(nm -> nm.send(p)); - } - private MinecraftServer getServer() { return ServerLifecycleHooks.getCurrentServer(); } diff --git a/src/main/java/net/neoforged/neoforge/network/PlayMessages.java b/src/main/java/net/neoforged/neoforge/network/PlayMessages.java deleted file mode 100644 index 97b6a91682..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/PlayMessages.java +++ /dev/null @@ -1,278 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import io.netty.buffer.Unpooled; -import java.util.Optional; -import java.util.UUID; -import net.minecraft.client.Minecraft; -import net.minecraft.client.gui.screens.MenuScreens; -import net.minecraft.client.gui.screens.Screen; -import net.minecraft.client.gui.screens.inventory.MenuAccess; -import net.minecraft.client.multiplayer.ClientLevel; -import net.minecraft.core.registries.BuiltInRegistries; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.Component; -import net.minecraft.network.protocol.game.ClientboundAddEntityPacket; -import net.minecraft.util.Mth; -import net.minecraft.world.entity.Entity; -import net.minecraft.world.entity.EntityType; -import net.minecraft.world.inventory.AbstractContainerMenu; -import net.minecraft.world.inventory.MenuType; -import net.minecraft.world.level.Level; -import net.minecraft.world.phys.Vec3; -import net.neoforged.neoforge.common.util.LogicalSidedProvider; -import net.neoforged.neoforge.entity.IEntityAdditionalSpawnData; - -public class PlayMessages { - /** - * Used to spawn a custom entity without the same restrictions as - * {@link ClientboundAddEntityPacket} - *

- * To customize how your entity is created clientside (instead of using the default factory provided to the - * {@link EntityType}) - * see {@link EntityType.Builder#setCustomClientFactory}. - */ - public static class SpawnEntity { - private final Entity entity; - private final int typeId; - private final int entityId; - private final UUID uuid; - private final double posX, posY, posZ; - private final byte pitch, yaw, headYaw; - private final int velX, velY, velZ; - private final FriendlyByteBuf buf; - - SpawnEntity(Entity e) { - this.entity = e; - this.typeId = BuiltInRegistries.ENTITY_TYPE.getId(e.getType()); //TODO: Codecs - this.entityId = e.getId(); - this.uuid = e.getUUID(); - this.posX = e.getX(); - this.posY = e.getY(); - this.posZ = e.getZ(); - this.pitch = (byte) Mth.floor(e.getXRot() * 256.0F / 360.0F); - this.yaw = (byte) Mth.floor(e.getYRot() * 256.0F / 360.0F); - this.headYaw = (byte) (e.getYHeadRot() * 256.0F / 360.0F); - Vec3 vec3d = e.getDeltaMovement(); - double d1 = Mth.clamp(vec3d.x, -3.9D, 3.9D); - double d2 = Mth.clamp(vec3d.y, -3.9D, 3.9D); - double d3 = Mth.clamp(vec3d.z, -3.9D, 3.9D); - this.velX = (int) (d1 * 8000.0D); - this.velY = (int) (d2 * 8000.0D); - this.velZ = (int) (d3 * 8000.0D); - this.buf = null; - } - - private SpawnEntity(int typeId, int entityId, UUID uuid, double posX, double posY, double posZ, byte pitch, byte yaw, byte headYaw, int velX, int velY, int velZ, FriendlyByteBuf buf) { - this.entity = null; - this.typeId = typeId; - this.entityId = entityId; - this.uuid = uuid; - this.posX = posX; - this.posY = posY; - this.posZ = posZ; - this.pitch = pitch; - this.yaw = yaw; - this.headYaw = headYaw; - this.velX = velX; - this.velY = velY; - this.velZ = velZ; - this.buf = buf; - } - - public static void encode(SpawnEntity msg, FriendlyByteBuf buf) { - buf.writeVarInt(msg.typeId); - buf.writeInt(msg.entityId); - buf.writeLong(msg.uuid.getMostSignificantBits()); - buf.writeLong(msg.uuid.getLeastSignificantBits()); - buf.writeDouble(msg.posX); - buf.writeDouble(msg.posY); - buf.writeDouble(msg.posZ); - buf.writeByte(msg.pitch); - buf.writeByte(msg.yaw); - buf.writeByte(msg.headYaw); - buf.writeShort(msg.velX); - buf.writeShort(msg.velY); - buf.writeShort(msg.velZ); - if (msg.entity instanceof IEntityAdditionalSpawnData entityAdditionalSpawnData) { - final FriendlyByteBuf spawnDataBuffer = new FriendlyByteBuf(Unpooled.buffer()); - - entityAdditionalSpawnData.writeSpawnData(spawnDataBuffer); - - buf.writeVarInt(spawnDataBuffer.readableBytes()); - buf.writeBytes(spawnDataBuffer); - - spawnDataBuffer.release(); - } else { - buf.writeVarInt(0); - } - } - - public static SpawnEntity decode(FriendlyByteBuf buf) { - return new SpawnEntity(buf.readVarInt(), buf.readInt(), new UUID(buf.readLong(), buf.readLong()), buf.readDouble(), buf.readDouble(), buf.readDouble(), buf.readByte(), buf.readByte(), buf.readByte(), buf.readShort(), buf.readShort(), buf.readShort(), readSpawnDataPacket(buf)); - } - - private static FriendlyByteBuf readSpawnDataPacket(FriendlyByteBuf buf) { - final int count = buf.readVarInt(); - if (count > 0) { - final FriendlyByteBuf spawnDataBuffer = new FriendlyByteBuf(Unpooled.buffer()); - spawnDataBuffer.writeBytes(buf, count); - return spawnDataBuffer; - } - - return new FriendlyByteBuf(Unpooled.buffer()); - } - - public static boolean handle(SpawnEntity msg, NetworkEvent.Context ctx) { - try { - EntityType type = BuiltInRegistries.ENTITY_TYPE.byId(msg.typeId); - Optional world = LogicalSidedProvider.CLIENTWORLD.get(ctx.getDirection().getReceptionSide()); - Entity e = world.map(w -> type.customClientSpawn(msg, w)).orElse(null); - if (e == null) { - return true; - } - - /* - * Sets the postiion on the client, Mirrors what - * Entity#recreateFromPacket and LivingEntity#recreateFromPacket does. - */ - e.syncPacketPositionCodec(msg.posX, msg.posY, msg.posZ); - e.absMoveTo(msg.posX, msg.posY, msg.posZ, (msg.yaw * 360) / 256.0F, (msg.pitch * 360) / 256.0F); - e.setYHeadRot((msg.headYaw * 360) / 256.0F); - e.setYBodyRot((msg.headYaw * 360) / 256.0F); - - e.setId(msg.entityId); - e.setUUID(msg.uuid); - world.filter(ClientLevel.class::isInstance).ifPresent(w -> ((ClientLevel) w).addEntity(e)); - e.lerpMotion(msg.velX / 8000.0, msg.velY / 8000.0, msg.velZ / 8000.0); - if (e instanceof IEntityAdditionalSpawnData entityAdditionalSpawnData) { - entityAdditionalSpawnData.readSpawnData(msg.buf); - } - } finally { - msg.buf.release(); - } - return true; - } - - public Entity getEntity() { - return entity; - } - - public int getTypeId() { - return typeId; - } - - public int getEntityId() { - return entityId; - } - - public UUID getUuid() { - return uuid; - } - - public double getPosX() { - return posX; - } - - public double getPosY() { - return posY; - } - - public double getPosZ() { - return posZ; - } - - public byte getPitch() { - return pitch; - } - - public byte getYaw() { - return yaw; - } - - public byte getHeadYaw() { - return headYaw; - } - - public int getVelX() { - return velX; - } - - public int getVelY() { - return velY; - } - - public int getVelZ() { - return velZ; - } - - public FriendlyByteBuf getAdditionalData() { - return buf; - } - } - - public static class OpenContainer { - private final int id; - private final int windowId; - private final Component name; - private final FriendlyByteBuf additionalData; - - OpenContainer(MenuType id, int windowId, Component name, FriendlyByteBuf additionalData) { - this(BuiltInRegistries.MENU.getId(id), windowId, name, additionalData); - } - - private OpenContainer(int id, int windowId, Component name, FriendlyByteBuf additionalData) { - this.id = id; - this.windowId = windowId; - this.name = name; - this.additionalData = additionalData; - } - - public static void encode(OpenContainer msg, FriendlyByteBuf buf) { - buf.writeVarInt(msg.id); - buf.writeVarInt(msg.windowId); - buf.writeComponent(msg.name); - buf.writeByteArray(msg.additionalData.readByteArray()); - } - - public static OpenContainer decode(FriendlyByteBuf buf) { - return new OpenContainer(buf.readVarInt(), buf.readVarInt(), buf.readComponent(), new FriendlyByteBuf(Unpooled.wrappedBuffer(buf.readByteArray(32600)))); - } - - public static boolean handle(OpenContainer msg, NetworkEvent.Context ctx) { - try { - MenuScreens.getScreenFactory(msg.getType(), Minecraft.getInstance(), msg.getWindowId(), msg.getName()).ifPresent(f -> { - AbstractContainerMenu c = msg.getType().create(msg.getWindowId(), Minecraft.getInstance().player.getInventory(), msg.getAdditionalData()); - - @SuppressWarnings("unchecked") - Screen s = ((MenuScreens.ScreenConstructor) f).create(c, Minecraft.getInstance().player.getInventory(), msg.getName()); - Minecraft.getInstance().player.containerMenu = ((MenuAccess) s).getMenu(); - Minecraft.getInstance().setScreen(s); - }); - } finally { - msg.getAdditionalData().release(); - } - return true; - } - - public final MenuType getType() { - return BuiltInRegistries.MENU.byId(this.id); - } - - public int getWindowId() { - return windowId; - } - - public Component getName() { - return name; - } - - public FriendlyByteBuf getAdditionalData() { - return additionalData; - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/PlayNetworkDirection.java b/src/main/java/net/neoforged/neoforge/network/PlayNetworkDirection.java deleted file mode 100644 index e9632dacfc..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/PlayNetworkDirection.java +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import it.unimi.dsi.fastutil.objects.Reference2ReferenceArrayMap; -import java.util.function.BiFunction; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.LogicalSide; -import net.neoforged.neoforge.network.custom.payload.SimplePayload; - -public enum PlayNetworkDirection implements INetworkDirection { - PLAY_TO_SERVER(NetworkEvent.ClientCustomPayloadEvent::new, LogicalSide.CLIENT, ServerboundCustomPayloadPacket.class, 1, (d, i, n) -> new ServerboundCustomPayloadPacket(SimplePayload.outbound(d, i, n))), - PLAY_TO_CLIENT(NetworkEvent.ServerCustomPayloadEvent::new, LogicalSide.SERVER, ClientboundCustomPayloadPacket.class, 0, (d, i, n) -> new ClientboundCustomPayloadPacket(SimplePayload.outbound(d, i, n))); - - private final BiFunction eventSupplier; - private final LogicalSide logicalSide; - private final Class> packetClass; - private final int otherWay; - private final Factory factory; - - private static final Reference2ReferenceArrayMap>, PlayNetworkDirection> packetLookup; - - static { - packetLookup = Stream.of(values()).collect(Collectors.toMap(PlayNetworkDirection::getPacketClass, Function.identity(), (m1, m2) -> m1, Reference2ReferenceArrayMap::new)); - } - - private PlayNetworkDirection(BiFunction eventSupplier, LogicalSide logicalSide, Class> clazz, int i, Factory factory) { - this.eventSupplier = eventSupplier; - this.logicalSide = logicalSide; - this.packetClass = clazz; - this.otherWay = i; - this.factory = factory; - } - - private Class> getPacketClass() { - return packetClass; - } - - public static > PlayNetworkDirection directionForPayload(Class customPacket) { - return packetLookup.get(customPacket); - } - - @Override - public PlayNetworkDirection reply() { - return PlayNetworkDirection.values()[this.otherWay]; - } - - public NetworkEvent getEvent(final ICustomPacketPayloadWithBuffer buffer, final NetworkEvent.Context manager) { - return this.eventSupplier.apply(buffer, manager); - } - - @Override - public LogicalSide getOriginationSide() { - return logicalSide; - } - - @Override - public LogicalSide getReceptionSide() { - return reply().logicalSide; - }; - - @Override - public Packet buildPacket(PacketData packetData, ResourceLocation channelName) { - return this.factory.create(packetData.buffer(), packetData.index(), channelName); - } - - private interface Factory> { - T create(FriendlyByteBuf data, int index, ResourceLocation channelName); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/ServerStatusPing.java b/src/main/java/net/neoforged/neoforge/network/ServerStatusPing.java deleted file mode 100644 index 3c6653fece..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/ServerStatusPing.java +++ /dev/null @@ -1,351 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network; - -import com.mojang.serialization.Codec; -import com.mojang.serialization.codecs.RecordCodecBuilder; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; -import java.util.function.Supplier; -import java.util.stream.Collectors; -import net.minecraft.Util; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.fml.IExtensionPoint; -import net.neoforged.fml.ModList; - -/** - * Represents additional data sent by FML when a server is pinged. - * Previous versions used the following format: - * - *

{@code
- * {
- *     "fmlNetworkVersion" : FMLNETVERSION,
- *     "channels": [
- *          {
- *              "res": "fml:handshake",
- *              "version": "1.2.3.4",
- *              "required": true
- *          }
- *     ],
- *     "mods": [
- *          {
- *              "modid": "modid",
- *              "modmarker": "{@literal }"
- *          }
- *     ]
- * }
- * }
- * - *

- * Due to size of the ping packet (32767 UTF-16 code points of JSON data) this could exceed this limit and - * cause issues. To work around this, a truncation mechanism was introduced, to heuristically truncate the size of the - * data, at the expense of making the compatibility info on the server screen inaccurate. - * - *

- * Modern versions will send binary data, which is encoded in a custom format optimized for UTF-16 code point count. - * See {@link #encodeOptimized(ByteBuf)} and {@link #decodeOptimized(String)}. - * Essentially 15 bits of binary data are encoded into every UTF-16 code point. The resulting string is then stored in - * the "d" property of the resulting JSON. - * - *

- * The "channels" and "mods" properties are retained for backwards compatibility, - * but left empty. A client that cannot read the old format would not be able to connect anyways, but the properties - * must exist to not cause exceptions. - * - *

{@code
- * {
- *     "fmlNetworkVersion": FMLNETVERSION,
- *     "channels": [],
- *     "mods": [],
- *     "d": "<binary data>"
- * }
- * }
- * - */ -public record ServerStatusPing( - Map channels, - Map mods, - int fmlNetworkVer, - boolean truncated) { - - private static final Codec BYTE_BUF_CODEC = Codec.STRING - .xmap(ServerStatusPing::decodeOptimized, ServerStatusPing::encodeOptimized); - - public static final Codec CODEC = RecordCodecBuilder.create( - in -> in - .group( - Codec.INT.fieldOf("fmlNetworkVersion").forGetter(ServerStatusPing::getFMLNetworkVersion), - - ServerStatusPing.BYTE_BUF_CODEC.optionalFieldOf("d").forGetter(ping -> Optional.of(ping.toBuf())), - - ChannelData.CODEC.listOf().optionalFieldOf("channels").forGetter(ping -> Optional.of(List.of())), - ModInfo.CODEC.listOf().optionalFieldOf("mods").forGetter(ping -> Optional.of(List.of())), - - // legacy versions see truncated lists, modern versions ignore this truncated flag (binary data has its own) - Codec.BOOL.optionalFieldOf("truncated").forGetter(ping -> Optional.of(ping.isTruncated()))) - .apply(in, - (fmlVer, buf, channels, mods, truncated) -> buf.map(byteBuf -> deserializeOptimized(fmlVer, byteBuf)) - .orElseGet(() -> new ServerStatusPing( - channels.orElseGet(List::of).stream().collect(Collectors.toMap(ChannelData::res, Function.identity())), - mods.orElseGet(List::of).stream().collect(Collectors.toMap(ModInfo::modId, ModInfo::modmarker)), - fmlVer, truncated.orElse(false))))); - - public ServerStatusPing() { - this( - NetworkRegistry.buildChannelVersionsForListPing(), - Util.make(new HashMap<>(), map -> ModList.get().forEachModContainer((modid, mc) -> map.put(modid, mc.getCustomExtension(IExtensionPoint.DisplayTest.class) - .map(IExtensionPoint.DisplayTest::suppliedVersion) - .map(Supplier::get) - .orElse(NetworkConstants.IGNORESERVERONLY)))), - NetworkConstants.FMLNETVERSION, - false); - } - - @Override // Don't compare the truncated flag as it is irrelevant - public boolean equals(Object o) { - if (this == o) return true; - if (!(o instanceof ServerStatusPing that)) return false; - return fmlNetworkVer == that.fmlNetworkVer && channels.equals(that.channels) && mods.equals(that.mods); - } - - @Override - public int hashCode() { - return Objects.hash(channels, mods, fmlNetworkVer); - } - - private List> getChannelsForMod(String modId) { - return channels.entrySet().stream() - .filter(c -> c.getKey().getNamespace().equals(modId)) - .toList(); - } - - private List> getNonModChannels() { - return channels.entrySet().stream() - .filter(c -> !mods.containsKey(c.getKey().getNamespace())) - .toList(); - } - - public ByteBuf toBuf() { - // The following techniques are used to keep the size down: - // 1. Try and group channels by ModID, this relies on the assumption that a mod "examplemod" uses a channel - // like "examplemod:network". In that case only the "path" of the ResourceLocation is written - // 2. Avoid sending IGNORESERVERONLY in plain text, instead use a flag (if set, no version string is sent) - // - // The size can be estimated as follows (assuming there are no non-mod network channels) - // bytes = 2 - // + mod_count * (avg_mod_id_length + avg_mod_version_length + 1) - // + (mod_count * avg_channel_count_per_mod) * (avg_mod_channel_length + avg_mod_channel_version_length + 1) - // + 1 - // for 600 mods with an average ModID and channel length of 20, an average channel of 1 per mod and an - // average version length of 5 this turns out to be 31203 bytes, which easily fits into the upper limit of - // roughly 60000 bytes. As such it is estimated that the upper limit will never be reached. - // we still check though and potentially truncate the list - var reachedSizeLimit = false; - var buf = new FriendlyByteBuf(Unpooled.buffer()); - buf.writeBoolean(false); // placeholder for whether we are truncating - buf.writeShort(mods.size()); // short so that we can replace it later in case of truncation - int writtenCount = 0; - for (var modEntry : mods.entrySet()) { - var isIgnoreServerOnly = modEntry.getValue().equals(NetworkConstants.IGNORESERVERONLY); - - var channelsForMod = getChannelsForMod(modEntry.getKey()); - var channelSizeAndVersionFlag = channelsForMod.size() << 1; - if (isIgnoreServerOnly) { - channelSizeAndVersionFlag |= VERSION_FLAG_IGNORESERVERONLY; - } - buf.writeVarInt(channelSizeAndVersionFlag); - - buf.writeUtf(modEntry.getKey()); - if (!isIgnoreServerOnly) { - buf.writeUtf(modEntry.getValue()); - } - - // write the channels for this mod, if any - for (var entry : channelsForMod) { - buf.writeUtf(entry.getKey().getPath()); - buf.writeUtf(entry.getValue().version()); - buf.writeBoolean(entry.getValue().required()); - } - - writtenCount++; - - if (buf.readableBytes() >= 60000) { - reachedSizeLimit = true; - break; - } - } - - if (!reachedSizeLimit) { - // write any channels that don't match up with a ModID. - var nonModChannels = getNonModChannels(); - buf.writeVarInt(nonModChannels.size()); - for (var entry : nonModChannels) { - buf.writeResourceLocation(entry.getKey()); - buf.writeUtf(entry.getValue().version()); - buf.writeBoolean(entry.getValue().required()); - } - } else { - buf.setShort(1, writtenCount); - buf.writeVarInt(0); - } - - buf.setBoolean(0, reachedSizeLimit); - return buf; - } - - private static final int VERSION_FLAG_IGNORESERVERONLY = 0b1; - - private static ServerStatusPing deserializeOptimized(int fmlNetworkVersion, ByteBuf bbuf) { - var buf = new FriendlyByteBuf(bbuf); - boolean truncated; - Map channels; - Map mods; - - try { - truncated = buf.readBoolean(); - var modsSize = buf.readUnsignedShort(); - mods = new HashMap<>(); - channels = new HashMap<>(); - for (var i = 0; i < modsSize; i++) { - var channelSizeAndVersionFlag = buf.readVarInt(); - var channelSize = channelSizeAndVersionFlag >>> 1; - var isIgnoreServerOnly = (channelSizeAndVersionFlag & VERSION_FLAG_IGNORESERVERONLY) != 0; - var modId = buf.readUtf(); - var modVersion = isIgnoreServerOnly ? NetworkConstants.IGNORESERVERONLY : buf.readUtf(); - for (var i1 = 0; i1 < channelSize; i1++) { - var channelName = buf.readUtf(); - var channelVersion = buf.readUtf(); - var requiredOnClient = buf.readBoolean(); - final ResourceLocation id = new ResourceLocation(modId, channelName); - channels.put(id, new ChannelData(id, channelVersion, requiredOnClient)); - } - - mods.put(modId, modVersion); - } - - var nonModChannelCount = buf.readVarInt(); - for (var i = 0; i < nonModChannelCount; i++) { - var channelName = buf.readResourceLocation(); - var channelVersion = buf.readUtf(); - var requiredOnClient = buf.readBoolean(); - channels.put(channelName, new ChannelData(channelName, channelVersion, requiredOnClient)); - } - } finally { - buf.release(); - } - - return new ServerStatusPing(channels, mods, fmlNetworkVersion, truncated); - } - - /** - * Encode given ByteBuf to a String. This is optimized for UTF-16 Code-Point count. - * Supports at most 2^30 bytes in length - */ - private static String encodeOptimized(ByteBuf buf) { - var byteLength = buf.readableBytes(); - var sb = new StringBuilder(); - sb.append((char) (byteLength & 0x7FFF)); - sb.append((char) ((byteLength >>> 15) & 0x7FFF)); - - int buffer = 0; // we will need at most 8 + 14 = 22 bits of buffer, so an int is enough - int bitsInBuf = 0; - while (buf.isReadable()) { - if (bitsInBuf >= 15) { - char c = (char) (buffer & 0x7FFF); - sb.append(c); - buffer >>>= 15; - bitsInBuf -= 15; - } - var b = buf.readUnsignedByte(); - buffer |= (int) b << bitsInBuf; - bitsInBuf += 8; - } - buf.release(); - - if (bitsInBuf > 0) { - char c = (char) (buffer & 0x7FFF); - sb.append(c); - } - return sb.toString(); - } - - /** - * Decode binary data encoded by {@link #encodeOptimized} - */ - private static ByteBuf decodeOptimized(String s) { - var size0 = ((int) s.charAt(0)); - var size1 = ((int) s.charAt(1)); - var size = size0 | (size1 << 15); - - var buf = Unpooled.buffer(size); - - int stringIndex = 2; - int buffer = 0; // we will need at most 8 + 14 = 22 bits of buffer, so an int is enough - int bitsInBuf = 0; - while (stringIndex < s.length()) { - while (bitsInBuf >= 8) { - buf.writeByte(buffer); - buffer >>>= 8; - bitsInBuf -= 8; - } - - var c = s.charAt(stringIndex); - buffer |= (((int) c) & 0x7FFF) << bitsInBuf; - bitsInBuf += 15; - stringIndex++; - } - - // write any leftovers - while (buf.readableBytes() < size) { - buf.writeByte(buffer); - buffer >>>= 8; - bitsInBuf -= 8; - } - return buf; - } - - public Map getRemoteChannels() { - return this.channels; - } - - public Map getRemoteModData() { - return mods; - } - - public int getFMLNetworkVersion() { - return fmlNetworkVer; - } - - public boolean isTruncated() { - return truncated; - } - - public record ModInfo(String modId, String modmarker) { - public static final Codec CODEC = RecordCodecBuilder.create( - in -> in - .group( - Codec.STRING.fieldOf("modId").forGetter(ModInfo::modId), - Codec.STRING.fieldOf("modmarker").forGetter(ModInfo::modmarker)) - .apply(in, ModInfo::new)); - } - - public record ChannelData(ResourceLocation res, String version, boolean required) { - public static final Codec CODEC = RecordCodecBuilder.create( - in -> in - .group( - ResourceLocation.CODEC.fieldOf("res").forGetter(ChannelData::res), - Codec.STRING.fieldOf("version").forGetter(ChannelData::version), - Codec.BOOL.fieldOf("required").forGetter(ChannelData::required)) - .apply(in, ChannelData::new)); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/bundle/BundlePacketUtils.java b/src/main/java/net/neoforged/neoforge/network/bundle/BundlePacketUtils.java new file mode 100644 index 0000000000..3afd5d9efc --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/bundle/BundlePacketUtils.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.bundle; + +import java.util.ArrayList; +import java.util.List; +import net.minecraft.network.PacketListener; +import net.minecraft.network.protocol.BundlePacket; +import net.minecraft.network.protocol.Packet; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class for dealing with {@link net.minecraft.network.protocol.BundlePacket}s. + */ +@ApiStatus.Internal +public class BundlePacketUtils { + + private BundlePacketUtils() { + throw new IllegalStateException("Tried to create utility class!"); + } + + public static List> flatten(Iterable> packets) { + final List> result = new ArrayList<>(); + packets.forEach(packet -> { + if (packet instanceof BundlePacket innerBundle) { + result.addAll(flatten(innerBundle.subPackets())); + } else { + result.add(packet); + } + }); + return result; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java b/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java new file mode 100644 index 0000000000..f35e9f908d --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/bundle/PacketAndPayloadAcceptor.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.bundle; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +public class PacketAndPayloadAcceptor { + + private final Consumer> consumer; + + public PacketAndPayloadAcceptor(Consumer> consumer) { + this.consumer = consumer; + } + + public PacketAndPayloadAcceptor accept(Packet packet) { + consumer.accept(packet); + return this; + } + + public PacketAndPayloadAcceptor accept(CustomPacketPayload payload) { + return accept(new ClientboundCustomPayloadPacket(payload)); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/ICustomConfigurationTask.java b/src/main/java/net/neoforged/neoforge/network/configuration/ICustomConfigurationTask.java new file mode 100644 index 0000000000..eb319d75aa --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/ICustomConfigurationTask.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.server.network.ConfigurationTask; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Defines a custom configuration task that should be run when a client connects. + *

+ * This interface is a wrapper functional interface around {@link ConfigurationTask}. + * Allowing for easily sending custom payloads to the client, without having to perform the wrapping + * in {@link ClientboundCustomPayloadPacket} yourself. + *
+ * It is recommended to use this interface over {@link ConfigurationTask} when you need to send custom payloads. + * It's functionality is otherwise identical. + *

+ */ +public interface ICustomConfigurationTask extends ConfigurationTask { + + /** + * Invoked when it is time for this configuration to run. + * + * @param sender A consumer that accepts a {@link CustomPacketPayload} to send to the client. + */ + void run(Consumer sender); + + /** + * Invoked when it is time for this configuration to run. + * + * @param sender A consumer that accepts a {@link Packet} to send to the client. + * @implNote Please do not override this method, it is implemented to wrap the {@link CustomPacketPayload} in a {@link ClientboundCustomPayloadPacket}. + */ + @Override + @ApiStatus.Internal + @ApiStatus.NonExtendable + default void start(@NotNull Consumer> sender) { + run((payload) -> sender.accept(new ClientboundCustomPayloadPacket(payload))); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseCompleted.java b/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseCompleted.java new file mode 100644 index 0000000000..bd5f254080 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseCompleted.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * A custom configuration task that is run when the modded configuration phase is completed. + * + * @param listener The listener that is used to configure the client. + */ +@ApiStatus.Internal +public record ModdedConfigurationPhaseCompleted(ServerConfigurationPacketListenerImpl listener) implements ICustomConfigurationTask { + private static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "modded_configuration_phase_completed"); + public static final Type TYPE = new Type(ID); + + @Override + public void run(Consumer sender) { + listener().onModdedConfigurationPhaseEnded(); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseStarted.java b/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseStarted.java new file mode 100644 index 0000000000..dc5115f0ae --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/ModdedConfigurationPhaseStarted.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Custom configuration task that is run to indicate that the modded configuration phase has started. + * + * @param listener The listener that is handling the configuration. + */ +@ApiStatus.Internal +public record ModdedConfigurationPhaseStarted(ServerConfigurationPacketListenerImpl listener) implements ICustomConfigurationTask { + private static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "modded_configuration_phase_started"); + public static final Type TYPE = new Type(ID); + + @Override + public void run(Consumer sender) { + listener().onModdedConfigurationPhaseStarted(); + } + + @Override + public @NotNull Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java b/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java new file mode 100644 index 0000000000..445ec67d90 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/SyncConfig.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.ConfigSync; +import org.jetbrains.annotations.ApiStatus; + +/** + * Configuration task that syncs the config files to the client + * + * @param listener the listener to indicate to that the task is complete + */ +@ApiStatus.Internal +public record SyncConfig(ServerConfigurationPacketListener listener) implements ICustomConfigurationTask { + private static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "sync_config"); + public static Type TYPE = new Type(ID); + + @Override + public void run(Consumer sender) { + ConfigSync.INSTANCE.syncConfigs().forEach(sender); + listener().finishCurrentTask(type()); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/SyncRegistries.java b/src/main/java/net/neoforged/neoforge/network/configuration/SyncRegistries.java new file mode 100644 index 0000000000..d67dd40b7b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/SyncRegistries.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; +import net.neoforged.neoforge.registries.RegistryManager; +import org.jetbrains.annotations.ApiStatus; + +/** + * Syncs registries to the client + */ +@ApiStatus.Internal +public record SyncRegistries() implements ICustomConfigurationTask { + private static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "sync_registries"); + public static final Type TYPE = new Type(ID); + + @Override + public void run(Consumer sender) { + sender.accept(new FrozenRegistrySyncStartPayload(RegistryManager.getRegistryNamesForSyncToClient())); + RegistryManager.generateRegistryPackets(false).forEach(sender); + sender.accept(new FrozenRegistrySyncCompletedPayload()); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/SyncTierSortingRegistry.java b/src/main/java/net/neoforged/neoforge/network/configuration/SyncTierSortingRegistry.java new file mode 100644 index 0000000000..d3049910ef --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/SyncTierSortingRegistry.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.configuration; + +import java.util.function.Consumer; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.common.TierSortingRegistry; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * Syncs the tier sorting registry to the client + * + * @param listener the listener to indicate the check if it is a vanilla connection + */ +@ApiStatus.Internal +public record SyncTierSortingRegistry(ServerConfigurationPacketListener listener) implements ICustomConfigurationTask { + private static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "sync_tier_sorting"); + public static final Type TYPE = new Type(ID); + + @Override + public void run(Consumer sender) { + TierSortingRegistry.sync(listener(), sender); + } + + @Override + public Type type() { + return TYPE; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/configuration/package-info.java b/src/main/java/net/neoforged/neoforge/network/configuration/package-info.java new file mode 100644 index 0000000000..fa97d17211 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/configuration/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package net.neoforged.neoforge.network.configuration; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/network/connection/ConnectionUtils.java b/src/main/java/net/neoforged/neoforge/network/connection/ConnectionUtils.java new file mode 100644 index 0000000000..80c3f55ff6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/connection/ConnectionUtils.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.connection; + +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AttributeKey; +import net.minecraft.network.Connection; +import org.jetbrains.annotations.ApiStatus; + +/** + * Utility class for storing and retrieving {@link Connection} objects from {@link ChannelHandlerContext} objects. + */ +public class ConnectionUtils { + + private ConnectionUtils() { + throw new IllegalStateException("Tried to create utility class!"); + } + + private static AttributeKey ATTRIBUTE_CONNECTION = AttributeKey.valueOf("neoforge:connection"); + + /** + * Gets the {@link Connection} object from the {@link ChannelHandlerContext} object. + * + * @param connection The {@link ChannelHandlerContext} object. + * @return The {@link Connection} object. + */ + public static Connection getConnection(ChannelHandlerContext connection) { + return connection.attr(ATTRIBUTE_CONNECTION).get(); + } + + /** + * Sets the {@link Connection} object to the {@link ChannelHandlerContext} object. + * + * @param connection The {@link ChannelHandlerContext} object. + * @param value The {@link Connection} object. + */ + @ApiStatus.Internal + public static void setConnection(ChannelHandlerContext connection, Connection value) { + connection.attr(ATTRIBUTE_CONNECTION).set(value); + } + + /** + * Removes the {@link Connection} object from the {@link ChannelHandlerContext} object. + * + * @param connection The {@link ChannelHandlerContext} object. + */ + @ApiStatus.Internal + public static void removeConnection(ChannelHandlerContext connection) { + connection.attr(ATTRIBUTE_CONNECTION).remove(); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/custom/payload/SimplePayload.java b/src/main/java/net/neoforged/neoforge/network/custom/payload/SimplePayload.java deleted file mode 100644 index 98f3441e7b..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/custom/payload/SimplePayload.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.custom.payload; - -import io.netty.buffer.Unpooled; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.ICustomPacketPayloadWithBuffer; - -public record SimplePayload(FriendlyByteBuf payload, ResourceLocation id, int packetIndex) implements ICustomPacketPayloadWithBuffer { - public SimplePayload(byte[] payload, ResourceLocation id, int packetIndex) { - this(new FriendlyByteBuf(Unpooled.wrappedBuffer(payload)), id, packetIndex); - } - - public static SimplePayload outbound(byte[] payload, int packetIndex, ResourceLocation id) { - return new SimplePayload(payload, id, packetIndex); - } - - public static SimplePayload outbound(FriendlyByteBuf byteBuf, int packetIndex, ResourceLocation id) { - return new SimplePayload(byteBuf, id, packetIndex); - } - - public static SimplePayload inbound(FriendlyByteBuf byteBuf, ResourceLocation id) { - final byte[] payload = new byte[byteBuf.readableBytes()]; - byteBuf.readBytes(payload); - final FriendlyByteBuf innerBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(payload)); - - return new SimplePayload(innerBuf, id, innerBuf.readVarInt()); - } - - @Override - public void write(FriendlyByteBuf buf) { - buf.writeVarInt(packetIndex); - buf.writeBytes(payload.slice()); - } - - @Override - public FriendlyByteBuf buffer() { - return new FriendlyByteBuf(Unpooled.copiedBuffer(payload)); - } - - @Override - public int packetIndex() { - return packetIndex; - } - - public FriendlyByteBuf payload() { - return payload; - } - -} diff --git a/src/main/java/net/neoforged/neoforge/network/custom/payload/SimpleQueryPayload.java b/src/main/java/net/neoforged/neoforge/network/custom/payload/SimpleQueryPayload.java deleted file mode 100644 index 659d84da0e..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/custom/payload/SimpleQueryPayload.java +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.custom.payload; - -import io.netty.buffer.Unpooled; -import java.util.Objects; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.ICustomQueryPayloadWithBuffer; -import org.jetbrains.annotations.NotNull; - -public final class SimpleQueryPayload implements ICustomQueryPayloadWithBuffer { - private final FriendlyByteBuf payload; - private final int packetIndex; - private final ResourceLocation id; - - private SimpleQueryPayload(byte[] payload, int packetIndex, ResourceLocation id) { - this.payload = new FriendlyByteBuf(Unpooled.wrappedBuffer(payload)); - this.packetIndex = packetIndex; - this.id = id; - } - - private SimpleQueryPayload(FriendlyByteBuf byteBuf, int packetIndex, ResourceLocation id) { - this.payload = byteBuf; - this.packetIndex = packetIndex; - this.id = id; - } - - public static SimpleQueryPayload outbound(byte[] payload, int packetIndex, ResourceLocation id) { - return new SimpleQueryPayload(payload, packetIndex, id); - } - - public static SimpleQueryPayload outbound(FriendlyByteBuf byteBuf, int packetIndex, ResourceLocation id) { - return new SimpleQueryPayload(byteBuf, packetIndex, id); - } - - public static SimpleQueryPayload inbound(FriendlyByteBuf byteBuf) { - final byte[] payload = new byte[byteBuf.readableBytes()]; - byteBuf.readBytes(payload); - final FriendlyByteBuf innerBuf = new FriendlyByteBuf(Unpooled.copiedBuffer(payload)); - - return new SimpleQueryPayload(innerBuf, innerBuf.readVarInt(), innerBuf.readResourceLocation()); - } - - public static SimpleQueryPayload inbound(FriendlyByteBuf byteBuf, ResourceLocation verifiableId) { - final var payload = inbound(byteBuf); - if (!payload.id().equals(verifiableId)) { - throw new IllegalStateException("The received payload did not indicate the same channel id as the received packet: %s vs: %s".formatted(payload.id(), verifiableId)); - } - - return payload; - } - - @Override - public void write(FriendlyByteBuf p_295179_) { - p_295179_.writeVarInt(packetIndex); - p_295179_.writeResourceLocation(id); - p_295179_.writeBytes(payload.slice()); - } - - @Override - public FriendlyByteBuf buffer() { - return new FriendlyByteBuf(Unpooled.copiedBuffer(payload)); - } - - @Override - public int packetIndex() { - return packetIndex; - } - - public FriendlyByteBuf payload() { - return payload; - } - - @Override - public @NotNull ResourceLocation id() { - return id; - } - - @Override - public boolean equals(Object obj) { - if (obj == this) return true; - if (obj == null || obj.getClass() != this.getClass()) return false; - var that = (SimpleQueryPayload) obj; - return Objects.equals(this.payload, that.payload) && - Objects.equals(this.id, that.id); - } - - @Override - public int hashCode() { - return Objects.hash(payload, id); - } - - @Override - public String toString() { - return "SimpleQueryPayload[" + - "payload=" + payload + ", " + - "id=" + id + ']'; - } - -} diff --git a/src/main/java/net/neoforged/neoforge/network/event/EventNetworkChannel.java b/src/main/java/net/neoforged/neoforge/network/event/EventNetworkChannel.java deleted file mode 100644 index 96d0622ca4..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/event/EventNetworkChannel.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.event; - -import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.function.Supplier; -import net.minecraft.network.Connection; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.NetworkInstance; -import net.neoforged.neoforge.network.NetworkRegistry; - -/** - * An event-bus like object on which {@link NetworkEvent}s are posted. - * - * These events are fired from the network thread, and so should not interact with most game state by default. - * {@link NetworkEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or client - * thread. - * - * @see NetworkRegistry#newEventChannel(ResourceLocation, Supplier, Predicate, Predicate) - * @see NetworkRegistry.ChannelBuilder#newEventChannel(ResourceLocation, Supplier, Predicate, Predicate) - */ -public class EventNetworkChannel { - private final NetworkInstance instance; - - public EventNetworkChannel(NetworkInstance instance) { - this.instance = instance; - } - - public void addListener(Consumer eventListener) { - instance.addListener(eventListener); - } - - public void registerObject(Object object) { - instance.registerObject(object); - } - - public void unregisterObject(Object object) { - instance.unregisterObject(object); - } - - public boolean isRemotePresent(Connection manager) { - return instance.isRemotePresent(manager); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/event/OnGameConfigurationEvent.java b/src/main/java/net/neoforged/neoforge/network/event/OnGameConfigurationEvent.java new file mode 100644 index 0000000000..10ca0d0924 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/event/OnGameConfigurationEvent.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.event; + +import java.util.LinkedList; +import java.util.Queue; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import net.neoforged.neoforge.network.configuration.ICustomConfigurationTask; +import org.jetbrains.annotations.ApiStatus; + +/** + * Fired when the server configuration packet listener collects all the configuration tasks + * that should be run on the server to configure the client. + */ +public class OnGameConfigurationEvent extends Event implements IModBusEvent { + + private final ServerConfigurationPacketListener listener; + + private final Queue configurationTasks = new LinkedList<>(); + + @ApiStatus.Internal + public OnGameConfigurationEvent(ServerConfigurationPacketListener listener) { + this.listener = listener; + } + + /** + * Register a configuration task to be run on the server. + * + * @param task The task to run. + */ + public void register(ICustomConfigurationTask task) { + configurationTasks.add(task); + } + + /** + * Get the configuration tasks that have been registered. + * + * @return The configuration tasks. + */ + @ApiStatus.Internal + public Queue getConfigurationTasks() { + return new LinkedList<>(configurationTasks); + } + + /** + * Get the server configuration packet listener. + * + * @return The server configuration packet listener. + */ + public ServerConfigurationPacketListener getListener() { + return listener; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/event/RegisterPayloadHandlerEvent.java b/src/main/java/net/neoforged/neoforge/network/event/RegisterPayloadHandlerEvent.java new file mode 100644 index 0000000000..a68dd88689 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/event/RegisterPayloadHandlerEvent.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.event; + +import java.util.function.Function; +import net.neoforged.bus.api.Event; +import net.neoforged.fml.event.IModBusEvent; +import net.neoforged.neoforge.network.registration.IPayloadRegistrar; +import net.neoforged.neoforge.network.registration.NetworkRegistry; +import org.jetbrains.annotations.ApiStatus; + +/** + * Event fired when the {@link NetworkRegistry} is being set up. + *

+ * This event is used to collect all the payload types and their handlers that should be used on the network. + *

+ */ +public class RegisterPayloadHandlerEvent extends Event implements IModBusEvent { + + private final Function registrarFactory; + + @ApiStatus.Internal + public RegisterPayloadHandlerEvent(Function registrarFactory) { + this.registrarFactory = registrarFactory; + } + + /** + * {@return A {@link IPayloadRegistrar} for the given namespace, creating one if it doesn't exist.} + */ + public IPayloadRegistrar registrar(String namespace) { + return registrarFactory.apply(namespace); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/filters/DynamicChannelHandler.java b/src/main/java/net/neoforged/neoforge/network/filters/DynamicChannelHandler.java new file mode 100644 index 0000000000..ecc30070fe --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/filters/DynamicChannelHandler.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.filters; + +import io.netty.channel.ChannelHandler; +import net.minecraft.network.Connection; +import org.jetbrains.annotations.ApiStatus; + +/** + * An extension to the netty {@link ChannelHandler} interface that allows for + * dynamic injection of handlers into the pipeline, based on whether they are needed + * on the current connection or not. + */ +@ApiStatus.Internal +public interface DynamicChannelHandler extends ChannelHandler { + + boolean isNecessary(Connection manager); +} diff --git a/src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java b/src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java new file mode 100644 index 0000000000..a9051cdf8c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/filters/GenericPacketSplitter.java @@ -0,0 +1,238 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.filters; + +import static net.minecraft.network.Connection.ATTRIBUTE_CLIENTBOUND_PROTOCOL; +import static net.minecraft.network.Connection.ATTRIBUTE_SERVERBOUND_PROTOCOL; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToMessageEncoder; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; +import java.util.ArrayList; +import java.util.List; +import net.minecraft.network.CompressionDecoder; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.VarInt; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.fml.common.Mod; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.handling.IPayloadContext; +import net.neoforged.neoforge.network.payload.SplitPacketPayload; +import net.neoforged.neoforge.network.registration.NetworkRegistry; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; + +/** + * A generic packet splitter that can be used to split packets that are too large to be sent in one go. + */ +@Mod.EventBusSubscriber(modid = NeoForgeVersion.MOD_ID, bus = Mod.EventBusSubscriber.Bus.MOD) +@ApiStatus.Internal +public class GenericPacketSplitter extends MessageToMessageEncoder> implements DynamicChannelHandler { + + private static final Logger LOGGER = LogManager.getLogger(); + + private static final int MAX_PACKET_SIZE = CompressionDecoder.MAXIMUM_UNCOMPRESSED_LENGTH; + private static final int MAX_PART_SIZE = determineMaxPayloadSize( + ConnectionProtocol.CONFIGURATION, + PacketFlow.SERVERBOUND); + + private static final byte STATE_FIRST = 1; + private static final byte STATE_LAST = 2; + + private final AttributeKey> codecKey; + + public GenericPacketSplitter(Connection connection) { + this(getProtocolKey(connection.getDirection().getOpposite())); + } + + public GenericPacketSplitter(AttributeKey> codecKey) { + this.codecKey = codecKey; + } + + @SubscribeEvent + private static void register(final RegisterPayloadHandlerEvent event) { + event.registrar(NeoForgeVersion.MOD_ID) + .versioned(NeoForgeVersion.getSpec()) + .optional() + .common( + SplitPacketPayload.ID, + SplitPacketPayload::new, + GenericPacketSplitter::receivedPacket); + } + + @Override + protected void encode(ChannelHandlerContext ctx, Packet packet, List out) throws Exception { + if (packet instanceof ClientboundCustomPayloadPacket clientboundCustomPayloadPacket && clientboundCustomPayloadPacket.payload() instanceof SplitPacketPayload) { + // Don't split our own split packets + out.add(packet); + return; + } + + if (packet instanceof ServerboundCustomPayloadPacket serverboundCustomPayloadPacket && serverboundCustomPayloadPacket.payload() instanceof SplitPacketPayload) { + // Don't split our own split packets + out.add(packet); + return; + } + + FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); + packet.write(buf); + if (buf.readableBytes() <= MAX_PACKET_SIZE) { + buf.release(); + out.add(packet); + } else { + int parts = (int) Math.ceil(((double) buf.readableBytes()) / MAX_PART_SIZE); + if (parts == 1) { + buf.release(); + out.add(packet); + } else { + + Attribute> attribute = ctx.channel().attr(this.codecKey); + ConnectionProtocol.CodecData codecdata = attribute.get(); + + final byte[] packetData = buf.array(); + for (int part = 0; part < parts; part++) { + final ByteBuf partPrefix; + if (part == 0) { + partPrefix = Unpooled.buffer(5); + partPrefix.writeByte(STATE_FIRST); + + VarInt.write(partPrefix, codecdata.packetId(packet)); + } else { + partPrefix = Unpooled.buffer(1); + partPrefix.writeByte(part == parts - 1 ? STATE_LAST : 0); + } + + final int partSize = Math.min(MAX_PART_SIZE, packetData.length - (part * MAX_PART_SIZE)); + final int prefixSize = partPrefix.readableBytes(); + final byte[] payloadSlice = new byte[partSize + prefixSize]; + + partPrefix.readBytes(payloadSlice, 0, prefixSize); + System.arraycopy(packetData, part * MAX_PART_SIZE, payloadSlice, prefixSize, partSize); + + out.add(createPacket(codecdata.flow(), payloadSlice)); + + partPrefix.release(); + } + // We cloned all the data into arrays, no need to retain the buffer anymore + buf.release(); + } + } + } + + private static final List receivedBuffers = new ArrayList<>(); + + private static void receivedPacket(SplitPacketPayload payload, IPayloadContext context) { + final ConnectionProtocol protocol = context.protocol(); + final PacketFlow flow = context.flow(); + final ChannelHandlerContext channelHandlerContext = context.channelHandlerContext(); + + byte state = payload.payload()[0]; + if (state == STATE_FIRST) { + if (!receivedBuffers.isEmpty()) { + LOGGER.warn("neoforge:split received out of order - inbound buffer not empty when receiving first"); + receivedBuffers.clear(); + } + } + + int contentSize = payload.payload().length - 1; + byte[] buffer = new byte[contentSize]; + System.arraycopy(payload.payload(), 1, buffer, 0, contentSize); // We cut of the initial byte here that indicates the state + receivedBuffers.add(buffer); + + if (state == STATE_LAST) { + final byte[][] buffers = receivedBuffers.toArray(new byte[0][]); + FriendlyByteBuf full = new FriendlyByteBuf(Unpooled.wrappedBuffer(buffers)); + int packetId = full.readVarInt(); + + Packet packet = protocol.codec(flow).createPacket(packetId, full, channelHandlerContext); + if (packet == null) { + LOGGER.error("Received invalid packet ID {} in neoforge:split", packetId); + } else { + receivedBuffers.clear(); + full.release(); + + context.workHandler() + .submitAsync(() -> context.packetHandler().handle(packet)) + .exceptionally(throwable -> { + LOGGER.error("Error handling packet", throwable); + return null; + }); + } + } + } + + private static Packet createPacket(PacketFlow flow, byte[] payload) { + return switch (flow) { + case SERVERBOUND -> new ServerboundCustomPayloadPacket(new SplitPacketPayload(payload)); + case CLIENTBOUND -> new ClientboundCustomPayloadPacket(new SplitPacketPayload(payload)); + }; + } + + @Override + public boolean isNecessary(Connection manager) { + return !manager.isMemoryConnection() && isRemoteCompatible(manager); + } + + public enum RemoteCompatibility { + ABSENT, + PRESENT + } + + public static RemoteCompatibility getRemoteCompatibility(Connection manager) { + return NetworkRegistry.getInstance().isVanillaConnection(manager) ? RemoteCompatibility.ABSENT : RemoteCompatibility.PRESENT; + } + + public static boolean isRemoteCompatible(Connection manager) { + return getRemoteCompatibility(manager) != RemoteCompatibility.ABSENT; + } + + public static int determineMaxPayloadSize(ConnectionProtocol protocol, PacketFlow flow) { + final FriendlyByteBuf temporaryBuf = new FriendlyByteBuf(Unpooled.buffer()); + int packetId = switch (flow) { + case SERVERBOUND -> protocol.codec(flow).packetId(new ServerboundCustomPayloadPacket(new SplitPacketPayload(new byte[0]))); + case CLIENTBOUND -> protocol.codec(flow).packetId(new ClientboundCustomPayloadPacket(new SplitPacketPayload(new byte[0]))); + }; + + //Simulate writing our split packet with a full byte array + //First write the packet id, as does the vanilla packet encoder. + temporaryBuf.writeVarInt(packetId); + + //Then write the payload id, as does the custom payload packet, regardless of flow. + temporaryBuf.writeResourceLocation(SplitPacketPayload.ID); + + //Then write the byte prefix to indicate the state of the packet. + temporaryBuf.writeByte(STATE_FIRST); + + //Then write the potential packet id of the split packet + temporaryBuf.writeVarInt(Integer.MAX_VALUE); + + //Now write a max int value for the maximum length of the byte[] + temporaryBuf.writeInt(Integer.MAX_VALUE); + + //During normal write operations, this is the prefix content that is written before the actual payload. + //This is the same for both clientbound and serverbound packets. + final int prefixSize = temporaryBuf.readableBytes(); + return CompressionDecoder.MAXIMUM_UNCOMPRESSED_LENGTH - prefixSize; + } + + private static AttributeKey> getProtocolKey(PacketFlow flow) { + return switch (flow) { + case CLIENTBOUND -> ATTRIBUTE_CLIENTBOUND_PROTOCOL; + case SERVERBOUND -> ATTRIBUTE_SERVERBOUND_PROTOCOL; + }; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/filters/NeoForgeConnectionNetworkFilter.java b/src/main/java/net/neoforged/neoforge/network/filters/NeoForgeConnectionNetworkFilter.java deleted file mode 100644 index 725f8848e1..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/filters/NeoForgeConnectionNetworkFilter.java +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.filters; - -import com.google.common.collect.ImmutableMap; -import io.netty.channel.ChannelHandler; -import java.util.List; -import java.util.Map; -import java.util.function.BiConsumer; -import net.minecraft.network.Connection; -import net.minecraft.network.ConnectionProtocol; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.PacketFlow; -import net.minecraft.network.protocol.common.ClientboundUpdateTagsPacket; -import net.minecraft.network.protocol.game.ClientboundLoginPacket; -import net.minecraft.network.protocol.game.ClientboundUpdateAdvancementsPacket; -import net.minecraft.network.protocol.game.ClientboundUpdateRecipesPacket; -import org.jetbrains.annotations.Nullable; - -/** - * Network filter for forge-forge connections. - */ -@ChannelHandler.Sharable -public class NeoForgeConnectionNetworkFilter extends VanillaPacketFilter { - public NeoForgeConnectionNetworkFilter(@Nullable Connection manager) { - super(buildHandlers(manager)); - } - - private static Map>, BiConsumer, List>>> buildHandlers(@Nullable Connection manager) { - - VanillaPacketSplitter.RemoteCompatibility compatibility = manager == null ? VanillaPacketSplitter.RemoteCompatibility.ABSENT : VanillaPacketSplitter.getRemoteCompatibility(manager); - if (compatibility == VanillaPacketSplitter.RemoteCompatibility.ABSENT) { - return ImmutableMap.of(); - } - ImmutableMap.Builder>, BiConsumer, List>>> builder = ImmutableMap.>, BiConsumer, List>>>builder() - .put(ClientboundUpdateRecipesPacket.class, NeoForgeConnectionNetworkFilter::splitPacket) - .put(ClientboundUpdateTagsPacket.class, NeoForgeConnectionNetworkFilter::splitPacket) - .put(ClientboundUpdateAdvancementsPacket.class, NeoForgeConnectionNetworkFilter::splitPacket) - .put(ClientboundLoginPacket.class, NeoForgeConnectionNetworkFilter::splitPacket); // When there are many dynamic registry entries that packet is BIG - - return builder.build(); - } - - @Override - protected boolean isNecessary(Connection manager) { - // not needed on local connections, because packets are not encoded to bytes there - return !manager.isMemoryConnection() && VanillaPacketSplitter.isRemoteCompatible(manager); - } - - private static void splitPacket(Packet packet, List> out) { - VanillaPacketSplitter.appendPackets( - ConnectionProtocol.PLAY, PacketFlow.CLIENTBOUND, packet, out); - } - -} diff --git a/src/main/java/net/neoforged/neoforge/network/filters/NetworkFilters.java b/src/main/java/net/neoforged/neoforge/network/filters/NetworkFilters.java index 337fdf8fce..57774898bc 100644 --- a/src/main/java/net/neoforged/neoforge/network/filters/NetworkFilters.java +++ b/src/main/java/net/neoforged/neoforge/network/filters/NetworkFilters.java @@ -7,27 +7,32 @@ import com.google.common.collect.ImmutableMap; import io.netty.channel.ChannelPipeline; +import java.util.List; import java.util.Map; import java.util.function.Function; import net.minecraft.network.Connection; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.ApiStatus; +@ApiStatus.Internal public class NetworkFilters { private static final Logger LOGGER = LogManager.getLogger(); - private static final Map> instances = ImmutableMap.of( + private static final Map> instances = ImmutableMap.of( "neoforge:vanilla_filter", manager -> new VanillaConnectionNetworkFilter(), - "neoforge:forge_fixes", NeoForgeConnectionNetworkFilter::new); + "neoforge:forge_fixes", GenericPacketSplitter::new); public static void injectIfNecessary(Connection manager) { + cleanIfNecessary(manager); + ChannelPipeline pipeline = manager.channel().pipeline(); if (pipeline.get("packet_handler") == null) return; // Realistically this can only ever be null if the connection was prematurely closed due to an error. We return early here to reduce further log spam. instances.forEach((key, filterFactory) -> { - VanillaPacketFilter filter = filterFactory.apply(manager); + DynamicChannelHandler filter = filterFactory.apply(manager); if (filter.isNecessary(manager)) { pipeline.addBefore("packet_handler", key, filter); LOGGER.debug("Injected {} into {}", filter, manager); @@ -35,6 +40,22 @@ public static void injectIfNecessary(Connection manager) { }); } + public static void cleanIfNecessary(Connection manager) { + ChannelPipeline pipeline = manager.channel().pipeline(); + if (pipeline.get("packet_handler") == null) + return; // Realistically this can only ever be null if the connection was prematurely closed due to an error. We return early here to reduce further log spam. + + //Grab the pipeline filters to remove in a seperate list to avoid a ConcurrentModificationException + final List toRemove = pipeline.names() + .stream() + .map(pipeline::get) + .filter(DynamicChannelHandler.class::isInstance) + .map(DynamicChannelHandler.class::cast) + .toList(); + + toRemove.forEach(pipeline::remove); + } + private NetworkFilters() {} } diff --git a/src/main/java/net/neoforged/neoforge/network/filters/VanillaConnectionNetworkFilter.java b/src/main/java/net/neoforged/neoforge/network/filters/VanillaConnectionNetworkFilter.java index bc9b718431..7bc3c74ff4 100644 --- a/src/main/java/net/neoforged/neoforge/network/filters/VanillaConnectionNetworkFilter.java +++ b/src/main/java/net/neoforged/neoforge/network/filters/VanillaConnectionNetworkFilter.java @@ -32,7 +32,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.tags.TagNetworkSerialization; -import net.neoforged.neoforge.network.NetworkHooks; +import net.neoforged.neoforge.network.registration.NetworkRegistry; import net.neoforged.neoforge.registries.RegistryManager; import org.jetbrains.annotations.NotNull; import org.slf4j.Logger; @@ -55,8 +55,8 @@ public VanillaConnectionNetworkFilter() { } @Override - protected boolean isNecessary(Connection manager) { - return NetworkHooks.isVanillaConnection(manager); + public boolean isNecessary(Connection manager) { + return NetworkRegistry.getInstance().isVanillaConnection(manager); } /** diff --git a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketFilter.java b/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketFilter.java index ac5d49100c..224f698f72 100644 --- a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketFilter.java +++ b/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketFilter.java @@ -19,7 +19,7 @@ /** * A filter for vanilla impl packets. */ -public abstract class VanillaPacketFilter extends MessageToMessageEncoder> { +public abstract class VanillaPacketFilter extends MessageToMessageEncoder> implements DynamicChannelHandler { protected final Map>, BiConsumer, List>>> handlers; @@ -46,7 +46,7 @@ protected static > Map.Entry>, BiC /** * Whether this filter is necessary on the given connection. */ - protected abstract boolean isNecessary(Connection manager); + public abstract boolean isNecessary(Connection manager); @Override protected void encode(ChannelHandlerContext ctx, Packet msg, List out) { diff --git a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java b/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java deleted file mode 100644 index d452a18d75..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/filters/VanillaPacketSplitter.java +++ /dev/null @@ -1,161 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.filters; - -import io.netty.buffer.ByteBuf; -import io.netty.buffer.Unpooled; -import java.util.ArrayList; -import java.util.List; -import java.util.function.Predicate; -import net.minecraft.network.CompressionDecoder; -import net.minecraft.network.Connection; -import net.minecraft.network.ConnectionProtocol; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.PacketListener; -import net.minecraft.network.protocol.Packet; -import net.minecraft.network.protocol.PacketFlow; -import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; -import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.ConnectionData; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.NetworkHooks; -import net.neoforged.neoforge.network.NetworkRegistry; -import net.neoforged.neoforge.network.PlayNetworkDirection; -import net.neoforged.neoforge.network.custom.payload.SimplePayload; -import net.neoforged.neoforge.network.event.EventNetworkChannel; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * A custom payload channel that allows sending vanilla server-to-client packets, even if they would normally - * be too large for the vanilla protocol. This is achieved by splitting them into multiple custom payload packets. - */ -public class VanillaPacketSplitter { - - private static final Logger LOGGER = LogManager.getLogger(); - - private static final ResourceLocation CHANNEL = new ResourceLocation("neoforge", "split"); - private static final String VERSION = "1.1"; - - private static final int PROTOCOL_MAX = CompressionDecoder.MAXIMUM_UNCOMPRESSED_LENGTH; - - private static final int PAYLOAD_TO_CLIENT_MAX = 1048576; - // 1 byte for state, 5 byte for VarInt PacketID - private static final int PART_SIZE = PAYLOAD_TO_CLIENT_MAX - 1 - 5; - - private static final byte STATE_FIRST = 1; - private static final byte STATE_LAST = 2; - - public static void register() { - Predicate versionCheck = NetworkRegistry.acceptMissingOr(VERSION); - EventNetworkChannel channel = NetworkRegistry.newEventChannel(CHANNEL, () -> VERSION, versionCheck, versionCheck); - channel.addListener(VanillaPacketSplitter::onClientPacket); - } - - /** - * Append the given packet to the given list. If the packet needs to be split, multiple packets will be appened. - * Otherwise only the packet itself. - */ - public static void appendPackets(ConnectionProtocol protocol, PacketFlow direction, Packet packet, List> out) { - if (heuristicIsDefinitelySmallEnough(packet)) { - out.add(packet); - } else { - FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); - packet.write(buf); - if (buf.readableBytes() <= PROTOCOL_MAX) { - buf.release(); - out.add(packet); - } else { - int parts = (int) Math.ceil(((double) buf.readableBytes()) / PART_SIZE); - if (parts == 1) { - buf.release(); - out.add(packet); - } else { - for (int part = 0; part < parts; part++) { - ByteBuf partPrefix; - if (part == 0) { - partPrefix = Unpooled.buffer(5); - partPrefix.writeByte(STATE_FIRST); - new FriendlyByteBuf(partPrefix).writeVarInt(protocol.codec(direction).packetId(packet)); - } else { - partPrefix = Unpooled.buffer(1); - partPrefix.writeByte(part == parts - 1 ? STATE_LAST : 0); - } - int partSize = Math.min(PART_SIZE, buf.readableBytes()); - ByteBuf partBuf = Unpooled.wrappedBuffer( - partPrefix, - buf.retainedSlice(buf.readerIndex(), partSize)); - buf.skipBytes(partSize); - out.add(new ClientboundCustomPayloadPacket(SimplePayload.outbound(new FriendlyByteBuf(partBuf), protocol.codec(direction).packetId(packet), CHANNEL))); - } - // we retained all the slices, so we do not need this one anymore - buf.release(); - } - } - } - } - - private static boolean heuristicIsDefinitelySmallEnough(Packet packet) { - return false; - } - - private static final List receivedBuffers = new ArrayList<>(); - - private static void onClientPacket(NetworkEvent.ServerCustomPayloadEvent event) { - NetworkEvent.Context ctx = event.getSource(); - PacketFlow direction = ctx.getDirection() == PlayNetworkDirection.PLAY_TO_CLIENT ? PacketFlow.CLIENTBOUND : PacketFlow.SERVERBOUND; - ConnectionProtocol protocol = ConnectionProtocol.PLAY; - - ctx.setPacketHandled(true); - - FriendlyByteBuf buf = event.getPayload(); - - byte state = buf.readByte(); - if (state == STATE_FIRST) { - if (!receivedBuffers.isEmpty()) { - LOGGER.warn("neoforge:split received out of order - inbound buffer not empty when receiving first"); - receivedBuffers.clear(); - } - } - buf.retain(); // retain the buffer, it is released after this handler otherwise - receivedBuffers.add(buf); - if (state == STATE_LAST) { - FriendlyByteBuf full = new FriendlyByteBuf(Unpooled.wrappedBuffer(receivedBuffers.toArray(new FriendlyByteBuf[0]))); - int packetId = full.readVarInt(); - Packet packet = protocol.codec(direction).createPacket(packetId, full); - if (packet == null) { - LOGGER.error("Received invalid packet ID {} in neoforge:split", packetId); - } else { - receivedBuffers.clear(); - full.release(); - ctx.enqueueWork(() -> genericsFtw(packet, event.getSource().getNetworkManager().getPacketListener())); - } - } - } - - @SuppressWarnings("unchecked") - private static void genericsFtw(Packet pkt, Object listener) { - pkt.handle((T) listener); - } - - public enum RemoteCompatibility { - ABSENT, - PRESENT - } - - public static RemoteCompatibility getRemoteCompatibility(Connection manager) { - ConnectionData connectionData = NetworkHooks.getConnectionData(manager); - if (connectionData != null && connectionData.getChannels().containsKey(CHANNEL)) { - return RemoteCompatibility.PRESENT; - } else { - return RemoteCompatibility.ABSENT; - } - } - - public static boolean isRemoteCompatible(Connection manager) { - return getRemoteCompatibility(manager) != RemoteCompatibility.ABSENT; - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java new file mode 100644 index 0000000000..b6bf2ab5ee --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ClientPayloadHandler.java @@ -0,0 +1,139 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handlers; + +import com.google.common.collect.Maps; +import com.google.common.collect.Sets; +import io.netty.buffer.Unpooled; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.MenuScreens; +import net.minecraft.client.gui.screens.Screen; +import net.minecraft.client.gui.screens.inventory.MenuAccess; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.inventory.AbstractContainerMenu; +import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.common.TierSortingRegistry; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import net.neoforged.neoforge.network.ConfigSync; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload; +import net.neoforged.neoforge.network.payload.AdvancedOpenScreenPayload; +import net.neoforged.neoforge.network.payload.ConfigFilePayload; +import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncStartPayload; +import net.neoforged.neoforge.network.payload.TierSortingRegistryPayload; +import net.neoforged.neoforge.registries.RegistryManager; +import net.neoforged.neoforge.registries.RegistrySnapshot; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class ClientPayloadHandler { + + private static final ClientPayloadHandler INSTANCE = new ClientPayloadHandler(); + + public static ClientPayloadHandler getInstance() { + return INSTANCE; + } + + private final Set toSynchronize = Sets.newConcurrentHashSet(); + private final Map synchronizedRegistries = Maps.newConcurrentMap(); + + private ClientPayloadHandler() {} + + public void handle(FrozenRegistryPayload payload, ConfigurationPayloadContext context) { + synchronizedRegistries.put(payload.registryName(), payload.snapshot()); + toSynchronize.remove(payload.registryName()); + } + + public void handle(FrozenRegistrySyncStartPayload payload, ConfigurationPayloadContext context) { + this.toSynchronize.addAll(payload.toAccess()); + this.synchronizedRegistries.clear(); + } + + public void handle(FrozenRegistrySyncCompletedPayload payload, ConfigurationPayloadContext context) { + if (!this.toSynchronize.isEmpty()) { + context.packetHandler().disconnect(Component.translatable("neoforge.network.registries.sync.missing", this.toSynchronize.stream().map(Object::toString).collect(Collectors.joining(", ")))); + return; + } + + context.workHandler().submitAsync(() -> { + //This method normally returns missing entries, but we just accept what the server send us and ignore the rest. + Set> keysUnknownToClient = RegistryManager.applySnapshot(synchronizedRegistries, false, false); + if (!keysUnknownToClient.isEmpty()) { + context.packetHandler().disconnect(Component.translatable("neoforge.network.registries.sync.server-with-unknown-keys", keysUnknownToClient.stream().map(Object::toString).collect(Collectors.joining(", ")))); + return; + } + + this.toSynchronize.clear(); + this.synchronizedRegistries.clear(); + }).exceptionally(e -> { + context.packetHandler().disconnect(Component.translatable("neoforge.network.registries.sync.failed", e.getMessage())); + return null; + }).thenAccept(v -> { + context.replyHandler().send(new FrozenRegistrySyncCompletedPayload()); + }); + } + + public void handle(ConfigFilePayload payload, ConfigurationPayloadContext context) { + ConfigSync.INSTANCE.receiveSyncedConfig(payload.contents(), payload.fileName()); + } + + public void handle(TierSortingRegistryPayload payload, ConfigurationPayloadContext context) { + TierSortingRegistry.handleSync(payload, context); + } + + public void handle(AdvancedAddEntityPayload advancedAddEntityPayload, PlayPayloadContext context) { + context.workHandler().submitAsync( + () -> { + Entity entity = Objects.requireNonNull(Minecraft.getInstance().level).getEntity(advancedAddEntityPayload.entityId()); + if (entity instanceof IEntityWithComplexSpawn entityAdditionalSpawnData) { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(advancedAddEntityPayload.customPayload())); + try { + entityAdditionalSpawnData.readSpawnData(buf); + } finally { + buf.release(); + } + } + }) + .exceptionally(e -> { + context.packetHandler().disconnect(Component.translatable("neoforge.network.advanced_add_entity.failed", e.getMessage())); + return null; + }); + } + + public void handle(AdvancedOpenScreenPayload msg, PlayPayloadContext context) { + context.workHandler().submitAsync(() -> { + final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.wrappedBuffer(msg.additionalData())); + try { + createMenuScreen(msg.name(), msg.menuType(), msg.windowId(), buf); + } finally { + buf.release(); + } + }).exceptionally(e -> { + context.packetHandler().disconnect(Component.translatable("neoforge.network.advanced_open_screen.failed", e.getMessage())); + return null; + }); + } + + private static void createMenuScreen(Component name, MenuType menuType, int windowId, FriendlyByteBuf buf) { + Minecraft mc = Minecraft.getInstance(); + MenuScreens.getScreenFactory(menuType, mc, windowId, name).ifPresent(f -> { + Screen s = f.create(menuType.create(windowId, mc.player.getInventory(), buf), mc.player.getInventory(), name); + mc.player.containerMenu = ((MenuAccess) s).getMenu(); + mc.setScreen(s); + }); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handlers/ServerPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handlers/ServerPayloadHandler.java new file mode 100644 index 0000000000..7b51ba8a99 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handlers/ServerPayloadHandler.java @@ -0,0 +1,33 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handlers; + +import net.neoforged.neoforge.network.configuration.SyncRegistries; +import net.neoforged.neoforge.network.configuration.SyncTierSortingRegistry; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.payload.FrozenRegistrySyncCompletedPayload; +import net.neoforged.neoforge.network.payload.TierSortingRegistrySyncCompletePayload; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public class ServerPayloadHandler { + + private static final ServerPayloadHandler INSTANCE = new ServerPayloadHandler(); + + public static ServerPayloadHandler getInstance() { + return INSTANCE; + } + + private ServerPayloadHandler() {} + + public void handle(FrozenRegistrySyncCompletedPayload payload, ConfigurationPayloadContext context) { + context.taskCompletedHandler().onTaskCompleted(SyncRegistries.TYPE); + } + + public void handle(TierSortingRegistrySyncCompletePayload payload, ConfigurationPayloadContext context) { + context.taskCompletedHandler().onTaskCompleted(SyncTierSortingRegistry.TYPE); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/ConfigurationPayloadContext.java b/src/main/java/net/neoforged/neoforge/network/handling/ConfigurationPayloadContext.java new file mode 100644 index 0000000000..c10ccf02ae --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/ConfigurationPayloadContext.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import io.netty.channel.ChannelHandlerContext; +import java.util.Optional; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.world.entity.player.Player; + +/** + * The context that is passed to a handler for a payload that arrives during the configuration phase of the connection. + * + * @param replyHandler A reply handler that can be used to send a reply to the player. + * @param packetHandler The packet handler that can be used to immediately process other packets. + * @param taskCompletedHandler The task completed handler that can be used to indicate that a configuration task has been completed. + * @param workHandler A work handler that can be used to schedule work to be done on the main thread. + * @param flow The flow of the packet. + * @param channelHandlerContext The channel handler context. + * @param player The player of the payload. + * @implNote The {@link #player()} will be filled with the current client side player if the payload was sent by the server, the server will only populate this field if it is not configuring the client. + */ +public record ConfigurationPayloadContext( + IReplyHandler replyHandler, + IPacketHandler packetHandler, + ITaskCompletedHandler taskCompletedHandler, + ISynchronizedWorkHandler workHandler, + PacketFlow flow, + ChannelHandlerContext channelHandlerContext, + Optional player) implements IPayloadContext { + @Override + public ConnectionProtocol protocol() { + return ConnectionProtocol.CONFIGURATION; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IConfigurationPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/IConfigurationPayloadHandler.java new file mode 100644 index 0000000000..5b809e13f5 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IConfigurationPayloadHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** + * Callback for handling custom packets. + * + * @param The type of payload. + */ +@FunctionalInterface +public interface IConfigurationPayloadHandler { + + /** + * Invoked to handle the given payload in the given context. + * + * @param payload The payload. + * @param context The context. + */ + void handle(T payload, ConfigurationPayloadContext context); + + /** + * Creates a replyHandler that does nothing. + * + * @return The replyHandler. + * @param The type of payload. + */ + static IConfigurationPayloadHandler noop() { + return (payload, context) -> {}; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IPacketHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/IPacketHandler.java new file mode 100644 index 0000000000..4f72bddae9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IPacketHandler.java @@ -0,0 +1,38 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** + * Describes a handler for a packet. + * Allows for the handling of full packets from custom payloads + */ +public interface IPacketHandler { + + /** + * Invoked to handle the given packet. + * + * @param packet The packet. + */ + void handle(Packet packet); + + /** + * Invoked to handle the given custom payload. + * + * @param payload The payload. + */ + void handle(CustomPacketPayload payload); + + /** + * Trigger a disconnect from the network. + * + * @param reason The reason for the disconnect. + */ + void disconnect(Component reason); +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IPayloadContext.java b/src/main/java/net/neoforged/neoforge/network/handling/IPayloadContext.java new file mode 100644 index 0000000000..e3124598e6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IPayloadContext.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import io.netty.channel.ChannelHandlerContext; +import java.util.Optional; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.player.Player; +import net.minecraft.world.level.Level; + +/** + * Defines a phase-less payload context that is passed to a handler for a payload that arrives during the connection. + */ +public interface IPayloadContext { + /** + * {@return a handler that can be used to reply to the payload} + */ + IReplyHandler replyHandler(); + + /** + * {@return a handler that can be used to have the current listener which received the payload handle another packet immediately} + */ + IPacketHandler packetHandler(); + + /** + * {@return a handler that can execute tasks on the main thread} + */ + ISynchronizedWorkHandler workHandler(); + + /** + * {@return the flow of the packet} + */ + PacketFlow flow(); + + /** + * {@return the protocol of the connection} + */ + ConnectionProtocol protocol(); + + /** + * {@return the channel handler context} + */ + ChannelHandlerContext channelHandlerContext(); + + /** + * {@return the player that acts within this context} + * + * @implNote This {@link Optional} will be filled with the current client side player if the payload was sent by the server, the server will only populate this field if it is not configuring the client. + */ + Optional player(); + + /** + * {@return the level acts within this context} + * + * @implNote This {@link Optional} will be filled with the current client side level if the payload was sent by the server, the server will only populate this field if it is not configuring the client. + */ + default Optional level() { + return player().map(Entity::level); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/IPayloadHandler.java new file mode 100644 index 0000000000..d70096143c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IPayloadHandler.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** + * Callback for handling custom packets. + * + * @param The type of payload. + */ +@FunctionalInterface +public interface IPayloadHandler { + + /** + * Invoked to handle the given payload in the given context. + * + * @param payload The payload. + * @param context The context. + */ + void handle(T payload, IPayloadContext context); +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IPlayPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/IPlayPayloadHandler.java new file mode 100644 index 0000000000..fe48c3fa9d --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IPlayPayloadHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** + * Callback for handling custom packets. + * + * @param The type of payload. + */ +@FunctionalInterface +public interface IPlayPayloadHandler { + + /** + * Invoked to handle the given payload in the given context. + * + * @param payload The payload. + * @param context The context. + */ + void handle(T payload, PlayPayloadContext context); + + /** + * Creates a replyHandler that does nothing. + * + * @return The replyHandler. + * @param The type of payload. + */ + static IPlayPayloadHandler noop() { + return (payload, context) -> {}; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/IReplyHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/IReplyHandler.java new file mode 100644 index 0000000000..ae3e72fe4f --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/IReplyHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; + +/** + * Interface for handling replies on custom packets. + */ +@FunctionalInterface +public interface IReplyHandler { + + /** + * Sends the given payload back to the player. + * + * @param payload The payload to send back. + */ + void send(CustomPacketPayload payload); +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/ISynchronizedWorkHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/ISynchronizedWorkHandler.java new file mode 100644 index 0000000000..4fbe9efd31 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/ISynchronizedWorkHandler.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Supplier; + +/** + * Defines a replyHandler which can accept work that needs to be run synchronously, on the main thread of the game. + */ +public interface ISynchronizedWorkHandler { + + /** + * Executes a task on the main thread of the game. + *

+ * The runnable task is protected against exceptions, and any exceptions thrown will be logged. + *

+ * + * @param task The task to run. + */ + void execute(Runnable task); + + /** + * Submits the given work to be run synchronously on the main thread of the game. + *

+ * This method will not be guarded against exceptions. + *
+ * If you need to guard against exceptions, call {@link CompletableFuture#exceptionally(Function)}, + * {@link CompletableFuture#exceptionallyAsync(Function)}}, or derivatives on the returned future. + *

+ * + * @param task The task to run. + */ + CompletableFuture submitAsync(Runnable task); + + /** + * Submits the given work to be run synchronously on the main thread of the game. + *

+ * This method will not be guarded against exceptions. + *
+ * If you need to guard against exceptions, call {@link CompletableFuture#exceptionally(Function)}, + * {@link CompletableFuture#exceptionallyAsync(Function)}}, or derivatives on the returned future. + *

+ * + * @param task The task to run. + * @return A future which will complete when the task has been run. + */ + CompletableFuture submitAsync(Supplier task); +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/ITaskCompletedHandler.java b/src/main/java/net/neoforged/neoforge/network/handling/ITaskCompletedHandler.java new file mode 100644 index 0000000000..e61e551bf6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/ITaskCompletedHandler.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import net.minecraft.server.network.ConfigurationTask; + +/** + * Handler which is called when a task is completed. + */ +@FunctionalInterface +public interface ITaskCompletedHandler { + + /** + * Called when a task is completed. + * + * @param type The type of task that was completed. + */ + void onTaskCompleted(ConfigurationTask.Type type); +} diff --git a/src/main/java/net/neoforged/neoforge/network/handling/PlayPayloadContext.java b/src/main/java/net/neoforged/neoforge/network/handling/PlayPayloadContext.java new file mode 100644 index 0000000000..31c630cb47 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/handling/PlayPayloadContext.java @@ -0,0 +1,36 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.handling; + +import io.netty.channel.ChannelHandlerContext; +import java.util.Optional; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.world.entity.player.Player; + +/** + * The context that is passed to a replyHandler for a payload that arrives during the configuration phase of the connection. + * + * @param replyHandler A reply replyHandler that can be used to send a reply to the player. + * @param packetHandler The packet replyHandler that can be used to immediately process other packets. + * @param workHandler A work replyHandler that can be used to schedule work to be done on the main thread. + * @param flow The flow of the packet. + * @param channelHandlerContext The channel replyHandler context. + * @param player The player of the payload. + * @implNote The {@link #player()} will be filled with the current client side player if the payload was sent by the server, the server will only populate this field if it is not configuring the client. + */ +public record PlayPayloadContext( + IReplyHandler replyHandler, + IPacketHandler packetHandler, + ISynchronizedWorkHandler workHandler, + PacketFlow flow, + ChannelHandlerContext channelHandlerContext, + Optional player) implements IPayloadContext { + @Override + public ConnectionProtocol protocol() { + return ConnectionProtocol.PLAY; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiableNetworkComponent.java b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiableNetworkComponent.java new file mode 100644 index 0000000000..d86545807e --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiableNetworkComponent.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.negotiation; + +import java.util.Optional; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents the input to the negotiation process for a single network payload type. + * + * @param id The id of the payload type. + * @param version The version of the payload type. + * @param flow The flow of the payload type. + * @param optional Whether the payload type is optional. + */ +@ApiStatus.Internal +public record NegotiableNetworkComponent( + ResourceLocation id, + Optional version, + Optional flow, + boolean optional) {} diff --git a/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiatedNetworkComponent.java b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiatedNetworkComponent.java new file mode 100644 index 0000000000..b6b249ef0b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiatedNetworkComponent.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.negotiation; + +import java.util.Optional; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a network component that has been negotiated between the client and server. + * + * @param id The id of the component + * @param version The version of the component, if any + */ +@ApiStatus.Internal +public record NegotiatedNetworkComponent( + ResourceLocation id, + Optional version) {} diff --git a/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiationResult.java b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiationResult.java new file mode 100644 index 0000000000..6fb959174a --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/negotiation/NegotiationResult.java @@ -0,0 +1,22 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.negotiation; + +import java.util.List; +import java.util.Map; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents the result of a negotiation of network components. + * + * @param components The successfully negotiated components. Is empty when {@link #success()} is false. + * @param success Whether the negotiation was successful. + * @param failureReasons The reasons for the failure of the negotiation. Is empty when {@link #success()} is true. + */ +@ApiStatus.Internal +public record NegotiationResult(List components, boolean success, Map failureReasons) {} diff --git a/src/main/java/net/neoforged/neoforge/network/negotiation/NetworkComponentNegotiator.java b/src/main/java/net/neoforged/neoforge/network/negotiation/NetworkComponentNegotiator.java new file mode 100644 index 0000000000..85f0854997 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/negotiation/NetworkComponentNegotiator.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.negotiation; + +import com.google.common.collect.HashBasedTable; +import com.google.common.collect.Maps; +import com.google.common.collect.Table; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import net.minecraft.network.chat.Component; +import net.minecraft.resources.ResourceLocation; +import org.apache.commons.compress.utils.Lists; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; + +/** + * Negotiates the network components between the server and client. + */ +@ApiStatus.Internal +public class NetworkComponentNegotiator { + + /** + * Negotiates the network components between the server and client. + *

+ * The following rules are followed: + *

    + *
  • Any component that is optional on the client but is not present on the server is removed from the client's list.
  • + *
  • Any component that is optional on the server but is not present on the client is removed from the server's list.
  • + *
  • If the client has none optional components that are not present on the server, then negotiation fails
  • + *
  • If the server has none optional components that are not present on the client, then negotiation fails
  • + *
  • For each of the matching channels the following is executed:
  • + *
      + *
    • Check if packet flow directions are set, and if at least one is set match it to the other, by missing or wrong value fail the negotiation.
    • + *
    • Check if both sides have the same version, or none set.
    • + *
    + *
  • At this point the channels are considered compatible, pick the servers version. It does not matter what side is picked since either both have the same version, or no version at all.
  • + *
+ *

+ *

+ * If negotiation succeeds then a list of agreed upon channels and their versions is returned. + *

+ *

+ * If negotiation fails then a {@link Component} is returned with the reason for failure. + *

+ * + * @param server The list of server components that the server wishes to use for communication. + * @param client The list of client components that the client wishes to use for communication. + * @return A {@link NegotiationResult} that contains the agreed upon channels and their versions if negotiation succeeded, or a {@link Component} with the reason for failure if negotiation failed. + */ + public static NegotiationResult negotiate(List server, List client) { + //Ensure the inputs are modifiable + server = new ArrayList<>(server); + client = new ArrayList<>(client); + + final List disabledOptionalOnClient = buildDisabledOptionalComponents(client, server); + + client.removeAll(disabledOptionalOnClient); + + List finalClient = client; + final List disabledOptionalOnServer = buildDisabledOptionalComponents(server, finalClient); + + server.removeAll(disabledOptionalOnServer); + + Table matches = HashBasedTable.create(); + server.forEach(s -> finalClient.forEach(c -> { + if (s.id().equals(c.id())) { + matches.put(s.id(), s, c); + } + })); + + client.removeIf(c -> matches.containsRow(c.id())); + server.removeIf(c -> matches.containsRow(c.id())); + + if (!client.isEmpty()) { + final Map failureReasons = Maps.newHashMap(); + client.forEach(c -> failureReasons.put(c.id(), Component.translatable("neoforge.network.negotiation.failure.missing.client.server"))); + return new NegotiationResult(List.of(), false, failureReasons); + } + + if (!server.isEmpty()) { + final Map failureReasons = Maps.newHashMap(); + server.forEach(c -> failureReasons.put(c.id(), Component.translatable("neoforge.network.negotiation.failure.missing.server.client"))); + return new NegotiationResult(List.of(), false, failureReasons); + } + + final List result = Lists.newArrayList(); + final Map failureReasons = Maps.newHashMap(); + for (Table.Cell match : matches.cellSet()) { + final NegotiableNetworkComponent serverComponent = match.getColumnKey(); + final NegotiableNetworkComponent clientComponent = match.getValue(); + + Optional serverToClientComparison = validateComponent(serverComponent, clientComponent, "client"); + if (serverToClientComparison.isPresent() && !serverToClientComparison.get().success()) { + failureReasons.put(serverComponent.id(), serverToClientComparison.get().failureReason()); + continue; + } + + Optional clientToServerComparison = validateComponent(clientComponent, serverComponent, "server"); + if (clientToServerComparison.isPresent() && !clientToServerComparison.get().success()) { + failureReasons.put(serverComponent.id(), clientToServerComparison.get().failureReason()); + continue; + } + + //We can take the servers version here. Either both sides have the same version, or both sides have no version. + result.add(new NegotiatedNetworkComponent(serverComponent.id(), serverComponent.version())); + } + + if (failureReasons.isEmpty()) { + return new NegotiationResult(result, true, failureReasons); + } + return new NegotiationResult(List.of(), false, failureReasons); + } + + /** + * Builds a list of disabled optional components. + * + * @param currentSide The current side to check for disabled optional components. + * @param otherSide The other side to check for missing components. + * @return The list of disabled optional components. + */ + @NotNull + private static List buildDisabledOptionalComponents(List currentSide, List otherSide) { + return currentSide.stream() + .filter(NegotiableNetworkComponent::optional) + .filter(c -> otherSide.stream().noneMatch(c2 -> c2.id().equals(c.id()))) + .toList(); + } + + /** + * Checks if two components are compatible. + *

+ * The following rules are followed: + *

    + *
  • Check if packet flow directions are set, and if at least one is set match it to the other, by missing or wrong value fail the negotiation.
  • + *
  • Check if both sides have the same version, or none set.
  • + *
+ *

+ *

+ * If negotiation succeeds then an empty {@link Optional} is returned. + *

+ *

+ * If negotiation fails then a {@link NegotiationResult} is returned with the reason for failure. + *

+ * + * @param left The verification component to compare. + * @param right The requesting component to compare. + * @param requestingSide The side of the requesting component. + * @return An empty {@link Optional} if negotiation succeeded, or a {@link NegotiationResult} with the reason for failure if negotiation failed. + */ + @VisibleForTesting + public static Optional validateComponent(NegotiableNetworkComponent left, NegotiableNetworkComponent right, String requestingSide) { + if (left.flow().isPresent()) { + if (right.flow().isEmpty()) { + return Optional.of(new ComponentNegotiationResult(false, Component.translatable("neoforge.network.negotiation.failure.flow.%s.missing".formatted(requestingSide), left.flow().get()))); + } else if (left.flow().get() != right.flow().get()) { + return Optional.of(new ComponentNegotiationResult(false, Component.translatable("neoforge.network.negotiation.failure.flow.%s.mismatch".formatted(requestingSide), left.flow().get(), right.flow().get()))); + } + } + + //If either side has no version set, fail + if (left.version().isEmpty() && right.version().isPresent()) { + return Optional.of(new ComponentNegotiationResult(false, Component.translatable("neoforge.network.negotiation.failure.version.%s.missing".formatted(requestingSide), right.version().get()))); + } + + //Check if both sides have the same version, or none set. + if (left.version().isPresent() && right.version().isPresent()) { + if (!left.version().get().equals(right.version().get())) { + return Optional.of(new ComponentNegotiationResult(false, Component.translatable("neoforge.network.negotiation.failure.version.mismatch", left.version().get(), right.version().get()))); + } + } + + //This happens when both the ranges are empty. + //In other words, no channel has a range, and no channel has a preferred version. + return Optional.empty(); + } + + /** + * The result of a negotiation. + * + * @param success If negotiation succeeded. + * @param failureReason The reason for failure if negotiation failed. + */ + public record ComponentNegotiationResult(boolean success, @Nullable Component failureReason) {} +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/AdvancedAddEntityPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/AdvancedAddEntityPayload.java new file mode 100644 index 0000000000..271e4c9c0b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/AdvancedAddEntityPayload.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.Entity; +import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * Payload that can be sent from the server to the client to add an entity to the world, with custom data. + * + * @param entityId The id of the entity to add. + * @param customPayload The custom data of the entity to add. + */ +@ApiStatus.Internal +public record AdvancedAddEntityPayload( + int entityId, + byte[] customPayload) implements CustomPacketPayload { + + /** + * The id of this payload. + */ + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "advanced_add_entity"); + + public AdvancedAddEntityPayload(FriendlyByteBuf buf) { + this( + buf.readVarInt(), + buf.readByteArray()); + } + + public AdvancedAddEntityPayload(Entity e) { + this( + e.getId(), + writeCustomData(e)); + } + + private static byte[] writeCustomData(final Entity entity) { + if (!(entity instanceof IEntityWithComplexSpawn additionalSpawnData)) { + return new byte[0]; + } + + return FriendlyByteBufUtil.writeCustomData(additionalSpawnData::writeSpawnData); + } + + @Override + public void write(FriendlyByteBuf buffer) { + buffer.writeVarInt(entityId()); + buffer.writeByteArray(customPayload()); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/AdvancedOpenScreenPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/AdvancedOpenScreenPayload.java new file mode 100644 index 0000000000..7b3e1a0012 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/AdvancedOpenScreenPayload.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.Objects; +import java.util.function.Consumer; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.inventory.MenuType; +import net.neoforged.neoforge.common.util.FriendlyByteBufUtil; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * A custom payload that allows for opening screens with additional data. + * + * @param windowId The window ID to use for the screen. + * @param menuType The menu type to open. + * @param name The name of the screen. + * @param additionalData The additional data to pass to the screen. + */ +@ApiStatus.Internal +public record AdvancedOpenScreenPayload( + int windowId, + MenuType menuType, + Component name, + byte[] additionalData) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "advanced_open_screen"); + + public AdvancedOpenScreenPayload(int windowId, MenuType menuType, Component name, Consumer dataWriter) { + this(windowId, menuType, name, FriendlyByteBufUtil.writeCustomData(dataWriter)); + } + + public AdvancedOpenScreenPayload(FriendlyByteBuf buffer) { + this(buffer.readVarInt(), Objects.requireNonNull(buffer.readById(BuiltInRegistries.MENU)), buffer.readComponentTrusted(), buffer.readByteArray()); + } + + @Override + public void write(FriendlyByteBuf buffer) { + buffer.writeVarInt(windowId()); + buffer.writeId(BuiltInRegistries.MENU, menuType()); + buffer.writeComponent(name()); + buffer.writeByteArray(additionalData()); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ConfigFilePayload.java b/src/main/java/net/neoforged/neoforge/network/payload/ConfigFilePayload.java new file mode 100644 index 0000000000..f0bf2761ad --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ConfigFilePayload.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * A payload that contains a config file. + *

+ * This is used to send config files to the client. + *

+ * + * @param contents The contents of the config file. + * @param fileName The name of the config file. + */ +@ApiStatus.Internal +public record ConfigFilePayload(byte[] contents, String fileName) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "config_file"); + + public ConfigFilePayload(FriendlyByteBuf buf) { + this(buf.readByteArray(), buf.readUtf()); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeByteArray(contents); + buf.writeUtf(fileName); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistryPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistryPayload.java new file mode 100644 index 0000000000..7515e60b29 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistryPayload.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.registries.RegistrySnapshot; +import org.jetbrains.annotations.ApiStatus; + +/** + * Packet payload for sending a frozen registry to the client + * + * @param registryName The name of the registry + * @param snapshot The snapshot of the registry + */ +@ApiStatus.Internal +public record FrozenRegistryPayload(ResourceLocation registryName, RegistrySnapshot snapshot) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "frozen_registry"); + + public FrozenRegistryPayload(FriendlyByteBuf buf) { + this(buf.readResourceLocation(), new RegistrySnapshot(buf)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeResourceLocation(registryName()); + snapshot().write(buf); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncCompletedPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncCompletedPayload.java new file mode 100644 index 0000000000..63b29f066b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncCompletedPayload.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * This payload is sent to the client when the server has finished sending all the frozen registries. + */ +@ApiStatus.Internal +public record FrozenRegistrySyncCompletedPayload() implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "frozen_registry_sync_completed"); + + public FrozenRegistrySyncCompletedPayload(FriendlyByteBuf buf) { + this(); + } + + @Override + public void write(FriendlyByteBuf buf) {} + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncStartPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncStartPayload.java new file mode 100644 index 0000000000..cfd605d006 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/FrozenRegistrySyncStartPayload.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.List; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * Packet payload sent to the client to start the frozen registry sync. + *

+ * It indicates to the client which registries it should expect to receive. + *

+ * + * @param toAccess The registries to access. + */ +@ApiStatus.Internal +public record FrozenRegistrySyncStartPayload(List toAccess) implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "frozen_registry_sync_start"); + + public FrozenRegistrySyncStartPayload(FriendlyByteBuf buf) { + this(buf.readList(FriendlyByteBuf::readResourceLocation)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeCollection(toAccess, FriendlyByteBuf::writeResourceLocation); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkComponent.java b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkComponent.java new file mode 100644 index 0000000000..bc5ba51bdc --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkComponent.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.Optional; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a modded network component, indicates what channel and version the client and server + * agreed upon. + * + * @param id The mod id + * @param version The mod version, if present + */ +@ApiStatus.Internal +public record ModdedNetworkComponent(ResourceLocation id, Optional version) { + + public ModdedNetworkComponent(FriendlyByteBuf buf) { + this(buf.readResourceLocation(), buf.readOptional(FriendlyByteBuf::readUtf)); + } + + public void write(FriendlyByteBuf buf) { + buf.writeResourceLocation(id); + buf.writeOptional(version, FriendlyByteBuf::writeUtf); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkPayload.java new file mode 100644 index 0000000000..6b63223bb2 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkPayload.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.HashSet; +import java.util.Set; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * A payload that contains the modded network configuration and play components. + * + * @param configuration The configuration components. + * @param play The play components. + */ +@ApiStatus.Internal +public record ModdedNetworkPayload(Set configuration, Set play) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation("network"); + public static final FriendlyByteBuf.Reader READER = ModdedNetworkPayload::new; + + public ModdedNetworkPayload(FriendlyByteBuf buf) { + this(buf.readCollection(HashSet::new, ModdedNetworkComponent::new), buf.readCollection(HashSet::new, ModdedNetworkComponent::new)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeObjectCollection(configuration(), ModdedNetworkComponent::write); + buf.writeObjectCollection(play(), ModdedNetworkComponent::write); + } + + @Override + public @NotNull ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryComponent.java b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryComponent.java new file mode 100644 index 0000000000..377dd4d4ba --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryComponent.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.Optional; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a potential modded network component, used for querying the client for modded network components. + * + * @param id The id of the component + * @param version The version of the component, if present + * @param flow The flow of the component, if present + * @param optional Whether the component is optional + */ +@ApiStatus.Internal +public record ModdedNetworkQueryComponent(ResourceLocation id, Optional version, Optional flow, boolean optional) { + + public ModdedNetworkQueryComponent(FriendlyByteBuf buf) { + this( + buf.readResourceLocation(), + buf.readOptional(FriendlyByteBuf::readUtf), + buf.readOptional(buffer -> buffer.readEnum(PacketFlow.class)), + buf.readBoolean()); + } + + public void write(FriendlyByteBuf buf) { + buf.writeResourceLocation(id); + buf.writeOptional(version, FriendlyByteBuf::writeUtf); + buf.writeOptional(flow, FriendlyByteBuf::writeEnum); + buf.writeBoolean(optional); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryPayload.java new file mode 100644 index 0000000000..1215ef087c --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkQueryPayload.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.HashSet; +import java.util.Set; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +/** + * Payload for the modded network query request + * + * @param configuration The configuration components + * @param play The play components + */ +@ApiStatus.Internal +public record ModdedNetworkQueryPayload(Set configuration, Set play) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation("register"); + public static final FriendlyByteBuf.Reader READER = ModdedNetworkQueryPayload::new; + + public ModdedNetworkQueryPayload() { + this(Set.of(), Set.of()); + } + + public ModdedNetworkQueryPayload(FriendlyByteBuf byteBuf) { + this(byteBuf.readCollection(HashSet::new, ModdedNetworkQueryComponent::new), byteBuf.readCollection(HashSet::new, ModdedNetworkQueryComponent::new)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeObjectCollection(configuration(), ModdedNetworkQueryComponent::write); + buf.writeObjectCollection(play(), ModdedNetworkQueryComponent::write); + } + + @Override + public @NotNull ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkSetupFailedPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkSetupFailedPayload.java new file mode 100644 index 0000000000..3670a5195e --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/ModdedNetworkSetupFailedPayload.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.Map; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * Payload sent to the client when the server has failed to set up the modded network. + * + * @param failureReasons A map of mod ids to the reason why the modded network failed to set up. + */ +@ApiStatus.Internal +public record ModdedNetworkSetupFailedPayload(Map failureReasons) implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "modded_network_setup_failed"); + public static final FriendlyByteBuf.Reader READER = ModdedNetworkSetupFailedPayload::new; + + public ModdedNetworkSetupFailedPayload(FriendlyByteBuf buf) { + this(buf.readMap(FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readComponent)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeMap(failureReasons, FriendlyByteBuf::writeResourceLocation, FriendlyByteBuf::writeComponent); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/SplitPacketPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/SplitPacketPayload.java new file mode 100644 index 0000000000..1bab592d3b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/SplitPacketPayload.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * A payload that is used to split a packet into multiple payloads. + *

+ * This single payload will contain a slice of the original packet. + *

+ * + * @param payload The slice of the original packet. + */ +@ApiStatus.Internal +public record SplitPacketPayload(byte[] payload) implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "split"); + + public SplitPacketPayload(FriendlyByteBuf buf) { + this(buf.readByteArray()); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeByteArray(payload); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistryPayload.java b/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistryPayload.java new file mode 100644 index 0000000000..f1352812a5 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistryPayload.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import java.util.List; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * The payload for the tier sorting registry packet. + *

+ * This payload is used to send the tier order to the client. + *

+ * + * @param tiers The tiers in order. + */ +@ApiStatus.Internal +public record TierSortingRegistryPayload(List tiers) implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "tier_sorting"); + + public TierSortingRegistryPayload(FriendlyByteBuf buf) { + this(buf.readList(FriendlyByteBuf::readResourceLocation)); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeCollection(tiers(), FriendlyByteBuf::writeResourceLocation); + } + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistrySyncCompletePayload.java b/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistrySyncCompletePayload.java new file mode 100644 index 0000000000..e1ec1580f5 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/TierSortingRegistrySyncCompletePayload.java @@ -0,0 +1,32 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import org.jetbrains.annotations.ApiStatus; + +/** + * This payload is sent by the server to the client when the tier sorting registry has been fully synced. + */ +@ApiStatus.Internal +public record TierSortingRegistrySyncCompletePayload() implements CustomPacketPayload { + public static final ResourceLocation ID = new ResourceLocation(NeoForgeVersion.MOD_ID, "tier_sorting_registry_sync_complete"); + + public TierSortingRegistrySyncCompletePayload(FriendlyByteBuf buf) { + this(); + } + + @Override + public void write(FriendlyByteBuf buf) {} + + @Override + public ResourceLocation id() { + return ID; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/payload/package-info.java b/src/main/java/net/neoforged/neoforge/network/payload/package-info.java new file mode 100644 index 0000000000..7d4744b496 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/payload/package-info.java @@ -0,0 +1,11 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +@MethodsReturnNonnullByDefault +@ParametersAreNonnullByDefault +package net.neoforged.neoforge.network.payload; + +import javax.annotation.ParametersAreNonnullByDefault; +import net.minecraft.MethodsReturnNonnullByDefault; diff --git a/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationPayloadHandler.java new file mode 100644 index 0000000000..ad2328a5b8 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationPayloadHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Optional; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An internal implementation of {@link IDirectionAwarePayloadHandlerBuilder} for {@link IConfigurationPayloadHandler}. + * + * @param The payload type. + */ +@ApiStatus.Internal +public final class ConfigurationPayloadHandler implements IConfigurationPayloadHandler { + + @Nullable + private final IConfigurationPayloadHandler clientSide; + @Nullable + private final IConfigurationPayloadHandler serverSide; + + private ConfigurationPayloadHandler(@Nullable IConfigurationPayloadHandler clientSide, @Nullable IConfigurationPayloadHandler serverSide) { + this.clientSide = clientSide; + this.serverSide = serverSide; + } + + @Override + public void handle(T payload, ConfigurationPayloadContext context) { + if (context.flow().isClientbound()) { + if (clientSide != null) { + clientSide.handle(payload, context); + } + } else if (context.flow().isServerbound()) { + if (serverSide != null) { + serverSide.handle(payload, context); + } + } + } + + Optional flow() { + if (clientSide == null && serverSide == null) { + return Optional.empty(); + } + + if (clientSide == null) { + return Optional.of(PacketFlow.SERVERBOUND); + } + + if (serverSide == null) { + return Optional.of(PacketFlow.CLIENTBOUND); + } + + return Optional.empty(); + } + + /** + * Internal builder for a configuration payload handler. + * + * @param The type of payload. + */ + static class Builder implements IDirectionAwarePayloadHandlerBuilder> { + private @Nullable IConfigurationPayloadHandler clientSide; + private @Nullable IConfigurationPayloadHandler serverSide; + + public Builder client(@NotNull IConfigurationPayloadHandler clientSide) { + this.clientSide = clientSide; + return this; + } + + public Builder server(@NotNull IConfigurationPayloadHandler serverSide) { + this.serverSide = serverSide; + return this; + } + + ConfigurationPayloadHandler create() { + return new ConfigurationPayloadHandler(clientSide, serverSide); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationRegistration.java b/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationRegistration.java new file mode 100644 index 0000000000..59b829a04b --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/ConfigurationRegistration.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Optional; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import org.jetbrains.annotations.ApiStatus; + +/** + * A record that holds the information needed to describe a registered configuration payload, its reader and handler. + * + * @param reader The reader for the payload + * @param handler The handler for the payload + * @param version The version of the payload + * @param flow The flow of the payload + * @param optional Whether the payload is optional + * @param The type of the payload + */ +@ApiStatus.Internal +public record ConfigurationRegistration( + FriendlyByteBuf.Reader reader, + IConfigurationPayloadHandler handler, + Optional version, + Optional flow, + boolean optional) implements IConfigurationPayloadHandler, FriendlyByteBuf.Reader { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void handle(CustomPacketPayload payload, ConfigurationPayloadContext context) { + ((IConfigurationPayloadHandler) handler).handle(payload, context); + } + + @Override + public CustomPacketPayload apply(FriendlyByteBuf buffer) { + return reader.apply(buffer); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/IDirectionAwarePayloadHandlerBuilder.java b/src/main/java/net/neoforged/neoforge/network/registration/IDirectionAwarePayloadHandlerBuilder.java new file mode 100644 index 0000000000..4fe47afa33 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/IDirectionAwarePayloadHandlerBuilder.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import org.jetbrains.annotations.NotNull; + +/** + * Defines a builder for a direction aware payload handler. + * + * @param

The type of payload. + * @param The type of handler. + */ +public interface IDirectionAwarePayloadHandlerBuilder

{ + + /** + * Sets the client side handler. + * + * @param clientSide The client side handler. + * @return This builder. + */ + IDirectionAwarePayloadHandlerBuilder client(@NotNull T clientSide); + + /** + * Sets the server side handler. + * + * @param serverSide The server side handler. + * @return This builder. + */ + IDirectionAwarePayloadHandlerBuilder server(@NotNull T serverSide); +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/IPayloadRegistrar.java b/src/main/java/net/neoforged/neoforge/network/registration/IPayloadRegistrar.java new file mode 100644 index 0000000000..37469bccc2 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/IPayloadRegistrar.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.function.Consumer; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ConfigurationTask; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; +import net.neoforged.neoforge.network.handling.ISynchronizedWorkHandler; +import net.neoforged.neoforge.network.handling.ITaskCompletedHandler; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; + +/** + * Defines a registrar for custom payloads that can be sent over the network. + *

+ * A custom payload is a class which extends {@link CustomPacketPayload}, it is recommended to use a record for this. + *

+ *

+ * The payload is written to the networks outgoing buffer using the {@link CustomPacketPayload#write(FriendlyByteBuf)} method. + * However, to read the payload from the incoming buffer, your registered {@link FriendlyByteBuf.Reader} is used. + *
+ * When you implement your {@link CustomPacketPayload#write(FriendlyByteBuf)} method you do not need to write the id of the payload, + * neither do you need to read it in your {@link FriendlyByteBuf.Reader} implementation. However, you do need to make sure that the + * id you pass into {@link #play(ResourceLocation, FriendlyByteBuf.Reader, IPlayPayloadHandler)} and + * {@link #configuration(ResourceLocation, FriendlyByteBuf.Reader, IConfigurationPayloadHandler)} is the same as the id you + * return from your {@link CustomPacketPayload#id()}. We suggest using a public static final ResourceLocation field + * to store it and then reference it in both places. + *
+ * Ids can be reused between play and configuration payloads, but it is needed to use different ids for different payloads. + *
+ * Under certain situations you are not able to register a payload: + *

    + *
  • If the id you are trying to register is already in use, meaning you used the same id twice for different packets of the same kind.
  • + *
  • If you are trying to register a payload to a namespace that is not your own.
  • + *
  • If the registrar has become invalid.
  • + *
+ * This means that the registration will fail if any of these cases occur. + * The exception thrown in these cases is a {@link RegistrationFailedException}. + *

+ *

+ * There are two kinds of payloads: + *

    + *
  • Play payloads: These are payloads that are sent from the client to the server, or from the server to the client, during normal gameplay.
  • + *
  • Configuration payloads: These are payloads that are sent from the server to the client, or from the client to the server, during the login process, before the player is spawned.
  • + *
+ * You can register a custom payload for either of these types of payloads using the {@link #play(ResourceLocation, FriendlyByteBuf.Reader, IPlayPayloadHandler)} + * and {@link #configuration(ResourceLocation, FriendlyByteBuf.Reader, IConfigurationPayloadHandler)} methods respectively. + *
+ * The difference between the play and configuration phases, if you like to call them that, is that the configuration phase generally requires + * a confirmation payload to be returned to the server to trigger the next phase. In the {@link ConfigurationPayloadContext context} passed into + * your {@link IConfigurationPayloadHandler} you will find a {@link ITaskCompletedHandler} which you can use, on the server side, + * to notify the connection management system that a given {@link ConfigurationTask.Type} has been completed. This will trigger the next phase of the + * login process. Invoking the {@link ITaskCompletedHandler#onTaskCompleted(ConfigurationTask.Type)} method on the client, will throw an exception. + *

+ *

+ * Note: the processing of payloads happens solely on the network thread. You are responsible for ensuring that any data you access + * in your handlers is either thread safe, or that you queue up your work to be done on the main thread, of the relevant side. + * This is particularly important for the {@link IPlayPayloadHandler} or {@link IConfigurationPayloadHandler} implementations that you pass to + * {@link #play(ResourceLocation, FriendlyByteBuf.Reader, IPlayPayloadHandler)} or {@link #configuration(ResourceLocation, FriendlyByteBuf.Reader, IConfigurationPayloadHandler)} + * respectively, since those are also invoked on the network thread. + *
+ * The {@link PlayPayloadContext} and {@link ConfigurationPayloadContext} given to each of these handlers contains a {@link ISynchronizedWorkHandler} + * which you can use to submit work to be run on the main thread of the game. This is the recommended way to handle any work that needs to be done + * on the main thread. + *
+ * Note the reader passed to any of the registration methods in this interface is invoked only if the payload is actually transferred over a connection which + * is not marked as {@link Connection#isMemoryConnection()}. This is important for single-player of lan-opened worlds since there the writer and reader are + * not invoked. That is because the payload is not actually transferred over the network, but only passed around in memory. + *

+ */ +public interface IPayloadRegistrar { + + /** + * Registers a new payload type for the play phase. + * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + * @implNote This method will capture all internal errors and wrap them in a {@link RegistrationFailedException}. + */ + IPayloadRegistrar play(ResourceLocation id, FriendlyByteBuf.Reader reader, IPlayPayloadHandler handler); + + /** + * Registers a new payload type for the play phase. + *

+ * This method allows different handlers to be registered for different packet-flows. + *
+ * In practice this means that you can register a different handler for clientbound and serverbound packets, + * which allows you to handle them differently on the client and server side. + *

+ * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + * @implNote This method will capture all internal errors and wrap them in a {@link RegistrationFailedException}. + */ + IPayloadRegistrar play(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler); + + /** + * Registers a new payload type for the configuration phase. + * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + * @implNote This method will capture all internal errors and wrap them in a {@link RegistrationFailedException}. + */ + IPayloadRegistrar configuration(ResourceLocation id, FriendlyByteBuf.Reader reader, IConfigurationPayloadHandler handler); + + /** + * Registers a new payload type for the configuration phase. + *

+ * This method allows different handlers to be registered for different packet-flows. + *
+ * In practice this means that you can register a different handler for clientbound and serverbound packets, + * which allows you to handle them differently on the client and server side. + *

+ * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + * @implNote This method will capture all internal errors and wrap them in a {@link RegistrationFailedException}. + */ + IPayloadRegistrar configuration(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler); + + /** + * Registers a new payload type for all supported phases. + * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + */ + default IPayloadRegistrar common(ResourceLocation id, FriendlyByteBuf.Reader reader, IPayloadHandler handler) { + return play(id, reader, handler::handle).configuration(id, reader, handler::handle); + } + + /** + * Registers a new payload type for all supported phases. + *

+ * This method allows different handlers to be registered for different packet-flows. + *
+ * In practice this means that you can register a different handler for clientbound and serverbound packets, + * which allows you to handle them differently on the client and server side. + *

+ * + * @param The type of the payload. + * @param id The id of the payload. + * @param reader The reader for the payload. + * @param handler The handler for the payload. + * @return The registrar. + */ + IPayloadRegistrar common(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler); + + /** + * Defines that the payloads registered by this registrar have a specific version associated with them. + * Clients connecting to a server with these payloads, will only be able to connect if they have the same version. + * + * @param version The version to use. + * @return A new registrar, ready to configure payloads with that version. + * @implNote The registrar implementation is immutable, so this method will return a new registrar. + */ + IPayloadRegistrar versioned(String version); + + /** + * Defines that the payloads registered by this registrar are optional. + * Clients connecting to a server which do not have the payloads registered, will still be able to connect. + *

+ * If clients have also a version set, and a version mismatch occurs (so both client and server have the payloads registered, + * yet have different versions), the connection attempt will fail. + * In other words, marking a payload as optional does not exempt it from versioning, if it has that configured. + *

+ * + * @return A new registrar, ready to configure payloads as optional. + * @implNote The registrar implementation is immutable, so this method will return a new registrar. + */ + IPayloadRegistrar optional(); +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/ModdedConfigurationPayloadRegistration.java b/src/main/java/net/neoforged/neoforge/network/registration/ModdedConfigurationPayloadRegistration.java new file mode 100644 index 0000000000..3f8a263950 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/ModdedConfigurationPayloadRegistration.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import org.jetbrains.annotations.ApiStatus; + +/** + * Registration for a custom packet payload. + * This type holds the negotiated preferredVersion of the payload to use, and the handler for it. + * + * @param id The id of the payload. + * @param type The type of payload. + * @param handler The handler for the payload. + * @param reader The reader for the payload. + * @param The type of payload. + */ +@ApiStatus.Internal +public record ModdedConfigurationPayloadRegistration( + ResourceLocation id, + Class type, + IConfigurationPayloadHandler handler, + FriendlyByteBuf.Reader reader) {} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/ModdedPacketRegistrar.java b/src/main/java/net/neoforged/neoforge/network/registration/ModdedPacketRegistrar.java new file mode 100644 index 0000000000..e8b641aedb --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/ModdedPacketRegistrar.java @@ -0,0 +1,146 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import java.util.Map; +import java.util.Optional; +import java.util.function.Consumer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; + +/** + * The internal implementation of {@link IPayloadRegistrar} for modded packets. + */ +@SuppressWarnings("OptionalUsedAsFieldOrParameterType") +class ModdedPacketRegistrar implements IPayloadRegistrar { + + private final String modId; + private final Map> configurationPayloads; + private final Map> playPayloads; + private Optional version = Optional.empty(); + private boolean optional = false; + private boolean valid = true; + + public ModdedPacketRegistrar(String modId) { + this.modId = modId; + playPayloads = Maps.newHashMap(); + configurationPayloads = Maps.newHashMap(); + } + + private ModdedPacketRegistrar(ModdedPacketRegistrar source) { + this.modId = source.modId; + this.playPayloads = source.playPayloads; + this.configurationPayloads = source.configurationPayloads; + this.version = source.version; + this.optional = source.optional; + this.valid = source.valid; + } + + public Map> getConfigurationRegistrations() { + return ImmutableMap.copyOf(configurationPayloads); + } + + public Map> getPlayRegistrations() { + return ImmutableMap.copyOf(playPayloads); + } + + @Override + public IPayloadRegistrar play(ResourceLocation id, FriendlyByteBuf.Reader reader, IPlayPayloadHandler handler) { + play( + id, new PlayRegistration<>( + reader, handler, version, Optional.empty(), optional)); + return this; + } + + @Override + public IPayloadRegistrar configuration(ResourceLocation id, FriendlyByteBuf.Reader reader, IConfigurationPayloadHandler handler) { + configuration( + id, new ConfigurationRegistration<>( + reader, handler, version, Optional.empty(), optional)); + return this; + } + + @Override + public IPayloadRegistrar play(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler) { + final PlayPayloadHandler.Builder builder = new PlayPayloadHandler.Builder<>(); + handler.accept(builder); + final PlayPayloadHandler innerHandler = builder.create(); + play( + id, new PlayRegistration<>( + reader, innerHandler, version, innerHandler.flow(), optional)); + return this; + } + + @Override + public IPayloadRegistrar configuration(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler) { + final ConfigurationPayloadHandler.Builder builder = new ConfigurationPayloadHandler.Builder<>(); + handler.accept(builder); + final ConfigurationPayloadHandler innerHandler = builder.create(); + configuration( + id, new ConfigurationRegistration<>( + reader, innerHandler, version, innerHandler.flow(), optional)); + return this; + } + + @Override + public IPayloadRegistrar common(ResourceLocation id, FriendlyByteBuf.Reader reader, Consumer>> handler) { + final PayloadHandlerBuilder builder = new PayloadHandlerBuilder<>(); + handler.accept(builder); + configuration(id, reader, builder::handleConfiguration); + play(id, reader, builder::handlePlay); + return this; + } + + private void configuration(final ResourceLocation id, ConfigurationRegistration registration) { + validatePayload(id, configurationPayloads); + + configurationPayloads.put(id, registration); + } + + private void play(final ResourceLocation id, PlayRegistration registration) { + validatePayload(id, playPayloads); + + playPayloads.put(id, registration); + } + + private void validatePayload(ResourceLocation id, final Map payloads) { + if (!valid) { + throw new RegistrationFailedException(id, modId, RegistrationFailedException.Reason.INVALID_REGISTRAR); + } + + if (payloads.containsKey(id)) { + throw new RegistrationFailedException(id, modId, RegistrationFailedException.Reason.DUPLICATE_ID); + } + + if (!id.getNamespace().equals(modId)) { + throw new RegistrationFailedException(id, modId, RegistrationFailedException.Reason.INVALID_NAMESPACE); + } + } + + @Override + public IPayloadRegistrar versioned(String version) { + final ModdedPacketRegistrar clone = new ModdedPacketRegistrar(this); + clone.version = Optional.of(version); + return clone; + } + + @Override + public IPayloadRegistrar optional() { + final ModdedPacketRegistrar clone = new ModdedPacketRegistrar(this); + clone.optional = true; + return clone; + } + + public void invalidate() { + valid = false; + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/ModdedPlayPayloadRegistration.java b/src/main/java/net/neoforged/neoforge/network/registration/ModdedPlayPayloadRegistration.java new file mode 100644 index 0000000000..4e81d5d6bb --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/ModdedPlayPayloadRegistration.java @@ -0,0 +1,29 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; +import org.jetbrains.annotations.ApiStatus; + +/** + * Registration for a custom packet payload. + * This type holds the negotiated preferredVersion of the payload to use, and the handler for it. + * + * @param id The id of the payload. + * @param type The type of payload. + * @param handler The handler for the payload. + * @param reader The reader for the payload. + * @param The type of payload. + */ +@ApiStatus.Internal +public record ModdedPlayPayloadRegistration( + ResourceLocation id, + Class type, + IPlayPayloadHandler handler, + FriendlyByteBuf.Reader reader) {} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/NetworkChannel.java b/src/main/java/net/neoforged/neoforge/network/registration/NetworkChannel.java new file mode 100644 index 0000000000..7bb85a5ebc --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/NetworkChannel.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Optional; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a complete negotiated network payload type, which is stored on the client and server. + * + * @param id The payload id. + * @param chosenVersion The chosen version, if any. + */ +@ApiStatus.Internal +public record NetworkChannel( + ResourceLocation id, + Optional chosenVersion) {} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/NetworkPayloadSetup.java b/src/main/java/net/neoforged/neoforge/network/registration/NetworkPayloadSetup.java new file mode 100644 index 0000000000..5ed4e3daee --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/NetworkPayloadSetup.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Map; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a complete negotiated network, which is stored on the client and server. + * + * @param configuration The configuration channels. + * @param play The play channels. + * @param vanilla Whether this is a vanilla network. + */ +@ApiStatus.Internal +public record NetworkPayloadSetup( + Map configuration, + Map play, + boolean vanilla) { + + /** + * {@return An empty modded network.} + */ + public static NetworkPayloadSetup emptyModded() { + return new NetworkPayloadSetup(Map.of(), Map.of(), false); + } + + /** + * {@return An empty vanilla network.} + */ + public static NetworkPayloadSetup emptyVanilla() { + return new NetworkPayloadSetup(Map.of(), Map.of(), true); + } + + /** + * {@return A modded network with the given configuration and play channels.} + */ + public static NetworkPayloadSetup from(Set configuration, Set play) { + return new NetworkPayloadSetup( + configuration.stream().collect(Collectors.toMap(NetworkChannel::id, Function.identity())), + play.stream().collect(Collectors.toMap(NetworkChannel::id, Function.identity())), + false); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java b/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java new file mode 100644 index 0000000000..f65fa8b02f --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/NetworkRegistry.java @@ -0,0 +1,959 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import com.mojang.logging.LogUtils; +import io.netty.channel.ChannelHandlerContext; +import io.netty.util.AttributeKey; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.function.Supplier; +import java.util.stream.Collectors; +import net.minecraft.network.Connection; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.PacketListener; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.ServerCommonPacketListener; +import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.BrandPayload; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.configuration.ClientConfigurationPacketListener; +import net.minecraft.network.protocol.configuration.ServerConfigurationPacketListener; +import net.minecraft.network.protocol.game.ClientGamePacketListener; +import net.minecraft.network.protocol.game.ServerGamePacketListener; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; +import net.minecraft.server.network.ServerPlayerConnection; +import net.minecraft.util.thread.ReentrantBlockableEventLoop; +import net.neoforged.fml.ModLoader; +import net.neoforged.fml.config.ConfigTracker; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.connection.ConnectionUtils; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.filters.NetworkFilters; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.IPacketHandler; +import net.neoforged.neoforge.network.handling.ISynchronizedWorkHandler; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.neoforge.network.negotiation.NegotiableNetworkComponent; +import net.neoforged.neoforge.network.negotiation.NegotiationResult; +import net.neoforged.neoforge.network.negotiation.NetworkComponentNegotiator; +import net.neoforged.neoforge.network.payload.ModdedNetworkComponent; +import net.neoforged.neoforge.network.payload.ModdedNetworkPayload; +import net.neoforged.neoforge.network.payload.ModdedNetworkQueryComponent; +import net.neoforged.neoforge.network.payload.ModdedNetworkQueryPayload; +import net.neoforged.neoforge.network.payload.ModdedNetworkSetupFailedPayload; +import org.apache.commons.compress.utils.Lists; +import org.jetbrains.annotations.ApiStatus; +import org.slf4j.Logger; + +/** + * Defines the registry for all modded network packets. + *

+ * This registry is responsible for storing all known modded network packets, and for handling the negotiation of modded network packets between the client and the server. + *

+ *

+ * Additionally, this registry is responsible for handling all modded network packets that are not natively known once they arrive at the receiving end. + *

+ *

+ * To prevent payloads from being send to a client that has no idea what to do with them, the registry provides endpoints for the vanilla code base to check if a packet can be send to a client. + *

+ */ +@ApiStatus.Internal +public class NetworkRegistry { + + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final AttributeKey ATTRIBUTE_PAYLOAD_SETUP = AttributeKey.valueOf("neoforge:payload_setup"); + private static final AttributeKey ATTRIBUTE_IS_MODDED_CONNECTION = AttributeKey.valueOf("neoforge:is_modded_connection"); + private static final AttributeKey ATTRIBUTE_FLOW = AttributeKey.valueOf("neoforge:flow"); + + private static final NetworkRegistry INSTANCE = new NetworkRegistry(); + + public static NetworkRegistry getInstance() { + return INSTANCE; + } + + private boolean setup = false; + private final Map> knownConfigurationRegistrations = Maps.newHashMap(); + private final Map> knownPlayRegistrations = Maps.newHashMap(); + + private NetworkRegistry() {} + + /** + * Invoked to initially set up the registry. + *

+ * This fires an event on the mod bus to allow mods to register their custom packets. And then stores the registered packets in the registry. + *

+ *

+ * This method can only be invoked once. + *

+ */ + public void setup() { + if (setup) + throw new IllegalStateException("The network registry can only be setup once."); + + setup = true; + + final Map registrarsByNamespace = Collections.synchronizedMap(new HashMap<>()); + ModLoader.get().postEvent(new RegisterPayloadHandlerEvent(namespace -> registrarsByNamespace.computeIfAbsent(namespace, ModdedPacketRegistrar::new))); + registrarsByNamespace.values().forEach(ModdedPacketRegistrar::invalidate); + + final ImmutableMap.Builder> configurationBuilder = ImmutableMap.builder(); + registrarsByNamespace.values().forEach(registrar -> registrar.getConfigurationRegistrations().forEach(configurationBuilder::put)); + + final ImmutableMap.Builder> playBuilder = ImmutableMap.builder(); + registrarsByNamespace.values().forEach(registrar -> registrar.getPlayRegistrations().forEach(playBuilder::put)); + + knownConfigurationRegistrations.clear(); + knownPlayRegistrations.clear(); + + knownConfigurationRegistrations.putAll(configurationBuilder.build()); + knownPlayRegistrations.putAll(playBuilder.build()); + } + + /** + * Invoked by the network subsystem to get a reader for a custom packet payload. + *

+ * This method special cases three situations: + *

    + *
  • Vanilla custom packets, they are defined as "known packets" and if the payload id matches the known vanilla reader is returned
  • + *
  • {@link ModdedNetworkQueryPayload}, it has a hardcoded id check, since it can be used before a network setup exists.
  • + *
  • {@link ModdedNetworkPayload}, it also has a hardcoded id check, since it can be used before a network setup exists, as well.
  • + *
  • {@link ModdedNetworkSetupFailedPayload}, it also has a hardcoded id check, since it can be used before a network setup exists, as well.
  • + *
+ *

+ *

+ * This method will then check if the connection is properly configured to be used for modded packets. + * If not a warning is logged, and null is returned. Which causes the packet to be discarded. + *

+ *

+ * If the connection is properly configured, the method will check if the packet is known to the connection, and if it is not, null is returned. + * Then the method will check if the packet is known to the registry, and if it is not, null is returned. + * If the packet is known to the registry, the method will return a reader that will invoke the registered replyHandler. + *

+ * + * @param id The id of the payload. + * @param context The context of the channel. + * @param protocol The protocol of the connection. + * @param knownTypes The known types of the connection. + * @return A reader for the payload, or null if the payload should be discarded. + */ + public FriendlyByteBuf.Reader getReader(ResourceLocation id, ChannelHandlerContext context, ConnectionProtocol protocol, Map> knownTypes) { + //Vanilla custom packet, let it deal with it. + if (knownTypes.containsKey(id)) { + return knownTypes.get(id); + } + + //These are our own custom modded packets which can be sent before a payload setup is negotiated. + //Special case them + if (id.equals(ModdedNetworkQueryPayload.ID)) { + return ModdedNetworkQueryPayload.READER; + } + + if (id.equals(ModdedNetworkPayload.ID)) { + return ModdedNetworkPayload.READER; + } + + if (id.equals(ModdedNetworkSetupFailedPayload.ID)) { + return ModdedNetworkSetupFailedPayload.READER; + } + + //Check the network setup. + final NetworkPayloadSetup payloadSetup = context.channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + final PacketFlow flow = context.channel().attr(ATTRIBUTE_FLOW).get(); + if (payloadSetup == null || flow == null) { + //Error: Bail. + LOGGER.warn("Received a modded custom payload packet {} that has not been negotiated with the server. Not parsing packet.", id); + return null; + } + + //Now ask the protocol what kind of payload is being sent and get the channel for it. + if (protocol.isPlay()) { + final NetworkChannel channel = payloadSetup.play().get(id); + + //Validate that everything is okey and then return a reader. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet {} with an unknown or not accepted channel. Not parsing packet.", id); + return null; + } + + final PlayRegistration registration = knownPlayRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet {} with an unknown or not accepted channel. Not parsing packet.", channel.id()); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + if (registration.flow().isPresent()) { + if (registration.flow().get() != flow) { + LOGGER.warn("Received a modded custom payload packet {} that is not supposed to be sent to the server. Disconnecting client.", channel.id()); + final Connection connection = ConnectionUtils.getConnection(context); + final PacketListener listener = connection.getPacketListener(); + if (listener instanceof ServerGamePacketListener serverListener) { + serverListener.disconnect(Component.translatable("neoforge.network.invalid_flow", flow)); + } else if (listener instanceof ClientGamePacketListener clientListener) { + clientListener.getConnection().disconnect(Component.translatable("neoforge.network.invalid_flow", flow)); + } else { + LOGGER.error("Received a modded custom payload packet {} that is not supposed to be sent to the server. Disconnecting client, but the listener is not a game listener. This should not happen.", channel.id()); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + } + } + + return registration; + } else if (protocol.isConfiguration()) { + final NetworkChannel channel = payloadSetup.configuration().get(id); + + //Also validate that everything is key and then return a reader. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet {} with an unknown or not accepted channel. Not parsing packet.", id); + return null; + } + + final ConfigurationRegistration registration = knownConfigurationRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet {} with an unknown or not accepted channel. Not parsing packet.", channel.id()); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + if (registration.flow().isPresent()) { + if (registration.flow().get() != flow) { + LOGGER.warn("Received a modded custom payload packet {} that is not supposed to be sent to the server. Disconnecting client.", channel.id()); + final Connection connection = ConnectionUtils.getConnection(context); + final PacketListener listener = connection.getPacketListener(); + if (listener instanceof ServerGamePacketListener serverListener) { + serverListener.disconnect(Component.translatable("neoforge.network.invalid_flow", flow)); + } else if (listener instanceof ClientGamePacketListener clientListener) { + clientListener.getConnection().disconnect(Component.translatable("neoforge.network.invalid_flow", flow)); + } else { + LOGGER.error("Received a modded custom payload packet {} that is not supposed to be sent to the server. Disconnecting client, but the listener is not a game listener. This should not happen.", channel.id()); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + } + } + + return registration; + } else { + //Error case, somebody is trying to sent a custom packet during a phase that is not supported. + LOGGER.error("Received a modded custom payload packet from a client that is not in the configuration or play phase. Not parsing packet."); + throw new IllegalStateException("A client sent a packet while not in the configuration or play phase. Somebody changed the phases known to NeoForge!"); + } + + } + + /** + * Invoked by a {@link ServerCommonPacketListener} when a modded packet is received on a modded connection that is not natively known to the vanilla code base. + *

+ * The method will first validate that a proper modded connection is setup and that a {@link NetworkPayloadSetup} is present on the connection. If that is not the case a warning is logged, and the client is disconnected with a generic error message. + *

+ *

+ * If the connection is setup properly, the method will check if the packet is known to the connection, and if it is not, the client is disconnected. Then checks are executed against the stored known packet handlers to see if the packet is known to the server. Technically, this list is considered fixed, based on the fact that the registration event is only fired once during bootstrap, so in practice this is just a safe-guard against people messing with the registration map. Once that completes the registered replyHandler is invoked. + *

+ * + * @param listener The listener which received the packet. + * @param packet The packet that was received. + */ + public void onModdedPacketAtServer(ServerCommonPacketListener listener, ServerboundCustomPayloadPacket packet) { + final NetworkPayloadSetup payloadSetup = listener.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + //Check if this client was even setup properly. + if (payloadSetup == null) { + LOGGER.warn("Received a modded custom payload packet from a client that has not negotiated with the server. Disconnecting client."); + listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return; + } + + if (listener instanceof ServerConfigurationPacketListener configurationPacketListener) { + //Get the configuration channel for the packet. + final NetworkChannel channel = payloadSetup.configuration().get(packet.payload().id()); + + //Check if the channel should even be processed. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet from a client with an unknown or not accepted channel. Disconnecting client."); + listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return; + } + + //We are in the configuration phase, so lookup packet listeners for that + final ConfigurationRegistration registration = knownConfigurationRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet from a client with an unknown or not accepted channel. Disconnecting client."); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + registration.handle( + packet.payload(), + new ConfigurationPayloadContext( + configurationPacketListener::send, + new ServerPacketHandler(configurationPacketListener), + configurationPacketListener::finishCurrentTask, + new EventLoopSynchronizedWorkHandler<>(configurationPacketListener.getMainThreadEventLoop(), packet.payload()), + PacketFlow.SERVERBOUND, + listener.getConnection().channel().pipeline().lastContext(), + Optional.empty())); + } else if (listener instanceof ServerGamePacketListener playPacketListener) { + //Get the configuration channel for the packet. + final NetworkChannel channel = payloadSetup.play().get(packet.payload().id()); + + //Check if the channel should even be processed. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet from a client with an unknown or not accepted channel. Disconnecting client."); + listener.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return; + } + + //We are in the play phase, so lookup packet listeners for that + final PlayRegistration registration = knownPlayRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet from a client with an unknown or not accepted channel. Disconnecting client."); + throw new IllegalStateException("A client sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + registration.handle( + packet.payload(), + new PlayPayloadContext( + playPacketListener::send, + new ServerPacketHandler(playPacketListener), + new EventLoopSynchronizedWorkHandler<>(playPacketListener.getMainThreadEventLoop(), packet.payload()), + PacketFlow.SERVERBOUND, + listener.getConnection().channel().pipeline().lastContext(), + listener instanceof ServerPlayerConnection connection ? Optional.of(connection.getPlayer()) : Optional.empty())); + } else { + LOGGER.error("Received a modded custom payload packet from a client that is not in the configuration or play phase. Disconnecting client."); + throw new IllegalStateException("A client sent a packet while not in the configuration or play phase. Somebody changed the phases known to NeoForge!"); + } + } + + /** + * Invoked by a {@link ClientCommonPacketListener} when a modded packet is received on a modded connection that is not natively known to the vanilla code base. + *

+ * The method will first validate that a proper modded connection is setup and that a {@link NetworkPayloadSetup} is present on the connection. If that is not the case a warning is logged, and the client is disconnected with a generic error message. + *

+ *

+ * If the connection is setup properly, the method will check if the packet is known to the connection, and if it is not, the client is disconnected. Then checks are executed against the stored known packet handlers to see if the packet is known to the client. Technically, this list is considered fixed, based on the fact that the registration event is only fired once during bootstrap, so in practice this is just a safe-guard against people messing with the registration map. Once that completes the registered replyHandler is invoked. + *

+ * + * @param listener The listener which received the packet. + * @param packet The packet that was received. + */ + public boolean onModdedPacketAtClient(ClientCommonPacketListener listener, ClientboundCustomPayloadPacket packet) { + if (packet.payload().id().getNamespace().equals("minecraft")) { + return false; + } + + final NetworkPayloadSetup payloadSetup = listener.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + //Check if this server was even setup properly. + if (payloadSetup == null) { + LOGGER.warn("Received a modded custom payload packet from a server that has not negotiated with the client. Disconnecting server."); + listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return false; + } + + if (listener instanceof ClientConfigurationPacketListener configurationPacketListener) { + //Get the configuration channel for the packet. + final NetworkChannel channel = payloadSetup.configuration().get(packet.payload().id()); + + //Check if the channel should even be processed. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet from a server with an unknown or not accepted channel. Disconnecting server."); + listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return false; + } + + //We are in the configuration phase, so lookup packet listeners for that + final ConfigurationRegistration registration = knownConfigurationRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet from a server with an unknown or not accepted channel. Disconnecting server."); + throw new IllegalStateException("A server sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + registration.handle( + packet.payload(), + new ConfigurationPayloadContext( + configurationPacketListener::send, + new ClientPacketHandler(configurationPacketListener), + (task) -> LOGGER.warn("Tried to finish a task on the client. This should not happen. Ignoring. Task: {}", task), + new EventLoopSynchronizedWorkHandler<>(configurationPacketListener.getMainThreadEventLoop(), packet.payload()), + PacketFlow.CLIENTBOUND, + listener.getConnection().channel().pipeline().lastContext(), + Optional.ofNullable(configurationPacketListener.getMinecraft().player))); + } else if (listener instanceof ClientGamePacketListener playPacketListener) { + //Get the configuration channel for the packet. + final NetworkChannel channel = payloadSetup.play().get(packet.payload().id()); + + //Check if the channel should even be processed. + if (channel == null) { + LOGGER.warn("Received a modded custom payload packet from a server with an unknown or not accepted channel. Disconnecting server."); + listener.getConnection().disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return false; + } + + //We are in the play phase, so lookup packet listeners for that + final PlayRegistration registration = knownPlayRegistrations.get(channel.id()); + if (registration == null) { + LOGGER.error("Received a modded custom payload packet from a server with an unknown or not accepted channel. Disconnecting server."); + throw new IllegalStateException("A server sent a packet with an unknown or not accepted channel, while negotiation succeeded. Somebody changed the channels known to NeoForge!"); + } + + registration.handle( + packet.payload(), + new PlayPayloadContext( + playPacketListener::send, + new ClientPacketHandler(playPacketListener), + new EventLoopSynchronizedWorkHandler<>(playPacketListener.getMainThreadEventLoop(), packet.payload()), + PacketFlow.CLIENTBOUND, + listener.getConnection().channel().pipeline().lastContext(), + Optional.ofNullable(playPacketListener.getMinecraft().player))); + } else { + LOGGER.error("Received a modded custom payload packet from a server that is not in the configuration or play phase. Disconnecting server."); + throw new IllegalStateException("A server sent a packet while not in the configuration or play phase. Somebody changed the phases known to NeoForge!"); + } + return true; + } + + /** + * Invoked by the server when it completes the negotiation with the client during the configuration phase. + *

+ * This method determines what the versions of each of the channels are, and checks if the client and server have a compatible set of network channels. + *

+ *

+ * If the negotiation fails, a custom packet is send to the client to inform it of the failure, and which will allow the client to disconnect gracefully with an indicative error screen. + *

+ *

+ * This method should only be invoked for modded connections. Use {@link #onVanillaConnectionDetectedAtServer(ServerConfigurationPacketListener)} to indicate that during the configuration phase of the network handshake between a client and the server, a vanilla connection was detected. + *

+ * + * @param sender The listener which completed the negotiation. + * @param configuration The configuration channels that the client has available. + * @param play The play channels that the client has available. + */ + public void onModdedConnectionDetectedAtServer(ServerConfigurationPacketListener sender, Set configuration, Set play) { + final NegotiationResult configurationNegotiationResult = NetworkComponentNegotiator.negotiate( + knownConfigurationRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList(), + configuration.stream() + .map(entry -> new NegotiableNetworkComponent(entry.id(), entry.version(), entry.flow(), entry.optional())) + .toList()); + + sender.getConnection().channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).set(true); + sender.getConnection().channel().attr(ATTRIBUTE_FLOW).set(PacketFlow.SERVERBOUND); + sender.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(NetworkPayloadSetup.emptyModded()); + + //Negotiation failed. Disconnect the client. + if (!configurationNegotiationResult.success()) { + if (!configurationNegotiationResult.failureReasons().isEmpty()) { + sender.send(new ModdedNetworkSetupFailedPayload(configurationNegotiationResult.failureReasons())); + } + + sender.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + return; + } + + final NegotiationResult playNegotiationResult = NetworkComponentNegotiator.negotiate( + knownPlayRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList(), + play.stream() + .map(entry -> new NegotiableNetworkComponent(entry.id(), entry.version(), entry.flow(), entry.optional())) + .toList()); + + //Negotiation failed. Disconnect the client. + if (!playNegotiationResult.success()) { + if (!playNegotiationResult.failureReasons().isEmpty()) { + sender.send(new ModdedNetworkSetupFailedPayload(playNegotiationResult.failureReasons())); + } + + sender.disconnect(Component.translatable("multiplayer.disconnect.incompatible", "NeoForge %s".formatted(NeoForgeVersion.getVersion()))); + } + + final NetworkPayloadSetup setup = NetworkPayloadSetup.from( + configurationNegotiationResult.components().stream() + .map(entry -> new NetworkChannel(entry.id(), entry.version())) + .collect(Collectors.toSet()), + playNegotiationResult.components().stream() + .map(entry -> new NetworkChannel(entry.id(), entry.version())) + .collect(Collectors.toSet())); + + sender.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(setup); + + NetworkFilters.injectIfNecessary(sender.getConnection()); + + sender.send(new ModdedNetworkPayload( + setup.configuration().values().stream().map(channel -> new ModdedNetworkComponent(channel.id(), channel.chosenVersion())).collect(Collectors.toSet()), + setup.play().values().stream().map(channel -> new ModdedNetworkComponent(channel.id(), channel.chosenVersion())).collect(Collectors.toSet()))); + } + + /** + * Invoked by the {@link ServerConfigurationPacketListenerImpl} when a vanilla connection is detected. + * + * @param sender The listener which detected the vanilla connection during the configuration phase. + * @return True if the vanilla connection should be handled by the server, false otherwise. + */ + public boolean onVanillaConnectionDetectedAtServer(ServerConfigurationPacketListener sender) { + NetworkFilters.cleanIfNecessary(sender.getConnection()); + + final NegotiationResult configurationNegotiationResult = NetworkComponentNegotiator.negotiate( + knownConfigurationRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList(), + List.of()); + + //Because we are in vanilla land, no matter what we are not able to support any custom channels. + sender.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(NetworkPayloadSetup.emptyVanilla()); + sender.getConnection().channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).set(false); + sender.getConnection().channel().attr(ATTRIBUTE_FLOW).set(PacketFlow.SERVERBOUND); + + //Negotiation failed. Disconnect the client. + if (!configurationNegotiationResult.success()) { + sender.disconnect(Component.translatable("neoforge.network.negotiation.failure.vanilla.client.not_supported", NeoForgeVersion.getVersion())); + return false; + } + + final NegotiationResult playNegotiationResult = NetworkComponentNegotiator.negotiate( + knownPlayRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList(), + List.of()); + + //Negotiation failed. Disconnect the client. + if (!playNegotiationResult.success()) { + sender.disconnect(Component.translatable("neoforge.network.negotiation.failure.vanilla.client.not_supported", NeoForgeVersion.getVersion())); + return false; + } + + NetworkFilters.injectIfNecessary(sender.getConnection()); + + return true; + } + + /** + * Indicates if the given packet can be sent via the given listener. + *

+ * This method is invoked by the vanilla code base to check if any packet can be sent to a client. It will always return true for a packet that is not a {@link ClientboundCustomPayloadPacket}. For a custom payload packet, it will check if the packet is known to the client, and if it is not, it will return false. + *

+ *

+ * If this method is invoked before the negotiation during the configuration phase has completed, and as such no {@link NetworkPayloadSetup} is present then it will only allow {@link ModdedNetworkQueryPayload} packets to be sent. + *

+ * + * @param packet The packet that is about to be sent. + * @param listener The listener that wants to send the packet. + * @return True if the packet can be sent, false otherwise. + */ + public boolean canSendPacket(Packet packet, ServerCommonPacketListener listener) { + if (!(packet instanceof ClientboundCustomPayloadPacket customPayloadPacket)) { + return true; + } + + if (shouldSendPacketRaw(packet)) { + return true; + } + + return isConnected(listener, customPayloadPacket.payload().id()); + } + + public boolean shouldSendPacketRaw(Packet packet) { + if (!(packet instanceof ClientboundCustomPayloadPacket customPayloadPacket)) { + return true; + } + + if (customPayloadPacket.payload() instanceof ModdedNetworkQueryPayload) { + return true; + } + + if (customPayloadPacket.payload() instanceof ModdedNetworkSetupFailedPayload) { + return true; + } + + if (customPayloadPacket.payload() instanceof ModdedNetworkPayload) { + return true; + } + + //Vanilla payloads. + return ClientboundCustomPayloadPacket.KNOWN_TYPES.containsKey(customPayloadPacket.payload().id()); + } + + /** + * Indicates if the given packet can be sent via the given listener. + *

+ * This method is invoked by the vanilla code base to check if any packet can be sent to a client. It will always return true for a packet that is not a {@link ServerboundCustomPayloadPacket}. For a custom payload packet, it will check if the packet is known to the server, and if it is not, it will return false. + *

+ *

+ * If this method is invoked before the negotiation during the configuration phase has completed, and as such no {@link NetworkPayloadSetup} is present then it will only allow {@link ModdedNetworkQueryPayload} packets to be sent. + *

+ * + * @param packet The packet that is about to be sent. + * @param listener The listener that wants to send the packet. + * @return True if the packet can be sent, false otherwise. + */ + public boolean canSendPacket(Packet packet, ClientCommonPacketListener listener) { + if (!(packet instanceof ServerboundCustomPayloadPacket customPayloadPacket)) { + return true; + } + + if (customPayloadPacket.payload() instanceof ModdedNetworkQueryPayload) { + return true; + } + + //Vanilla payloads. + if (ServerboundCustomPayloadPacket.KNOWN_TYPES.containsKey(customPayloadPacket.payload().id())) { + return true; + } + + return isConnected(listener, customPayloadPacket.payload().id()); + } + + /** + * Invoked by the client when a modded server queries it for its available channels. The negotiation happens solely on the server side, and the result is later transmitted to the client. + * + * @param listener The listener which received the query. + */ + public void onNetworkQuery(ClientConfigurationPacketListener listener) { + final ModdedNetworkQueryPayload payload = new ModdedNetworkQueryPayload( + knownConfigurationRegistrations.entrySet().stream() + .map(entry -> new ModdedNetworkQueryComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .collect(Collectors.toSet()), + knownPlayRegistrations.entrySet().stream() + .map(entry -> new ModdedNetworkQueryComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .collect(Collectors.toSet())); + + listener.send(payload); + } + + /** + * Invoked by the client to indicate that it detect a connection to a modded server, by receiving a {@link ModdedNetworkPayload}. This will configure the active connection to the server to use the channels that were negotiated. + *

+ * Once this method completes a {@link NetworkPayloadSetup} will be present on the connection. + *

+ * + * @param listener The listener which received the payload. + * @param configuration The configuration channels that were negotiated. + * @param play The play channels that were negotiated. + */ + public void onModdedNetworkConnectionEstablished(ClientConfigurationPacketListener listener, Set configuration, Set play) { + final NetworkPayloadSetup setup = NetworkPayloadSetup.from( + configuration.stream() + .map(entry -> new NetworkChannel(entry.id(), entry.version())) + .collect(Collectors.toSet()), + play.stream() + .map(entry -> new NetworkChannel(entry.id(), entry.version())) + .collect(Collectors.toSet())); + + NetworkFilters.injectIfNecessary(listener.getConnection()); + + listener.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(setup); + listener.getConnection().channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).set(true); + listener.getConnection().channel().attr(ATTRIBUTE_FLOW).set(PacketFlow.CLIENTBOUND); + } + + /** + * Invoked by the client when no {@link ModdedNetworkQueryPayload} has been received, but instead a {@link BrandPayload} has been received as the first packet during negotiation in the configuration phase. + *

+ * If this happens then the client will do a negotiation of its own internal channel configuration, to check if mods are installed which need a modded connection to the server. If those are found then the connection is aborted and the client disconnects from the server. + *

+ *

+ * This method should never be invoked on a connection where the serverside is a modded environment. + *

+ * + * @param sender The listener which received the brand payload. + * @return True if the vanilla connection should be handled by the client, false otherwise. + */ + public boolean onVanillaNetworkConnectionEstablished(ClientConfigurationPacketListener sender) { + NetworkFilters.cleanIfNecessary(sender.getConnection()); + + final NegotiationResult configurationNegotiationResult = NetworkComponentNegotiator.negotiate( + List.of(), + knownConfigurationRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList()); + + //Because we are in vanilla land, no matter what we are not able to support any custom channels. + sender.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(NetworkPayloadSetup.emptyVanilla()); + sender.getConnection().channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).set(false); + sender.getConnection().channel().attr(ATTRIBUTE_FLOW).set(PacketFlow.CLIENTBOUND); + + //Negotiation failed. Disconnect the client. + if (!configurationNegotiationResult.success()) { + sender.getConnection().disconnect(Component.translatable("neoforge.network.negotiation.failure.vanilla.client.not_supported", NeoForgeVersion.getVersion())); + return false; + } + + final NegotiationResult playNegotiationResult = NetworkComponentNegotiator.negotiate( + List.of(), + knownPlayRegistrations.entrySet().stream() + .map(entry -> new NegotiableNetworkComponent(entry.getKey(), entry.getValue().version(), entry.getValue().flow(), entry.getValue().optional())) + .toList()); + + //Negotiation failed. Disconnect the client. + if (!playNegotiationResult.success()) { + sender.getConnection().disconnect(Component.translatable("neoforge.network.negotiation.failure.vanilla.client.not_supported", NeoForgeVersion.getVersion())); + return false; + } + + //We are on the client, connected to a vanilla server, We have to load the default configs. + ConfigTracker.INSTANCE.loadDefaultServerConfigs(); + + NetworkFilters.injectIfNecessary(sender.getConnection()); + + return true; + } + + /** + * Indicates if the given connection is a vanilla connection. + * + * @param connection The connection to check. + * @return True if the connection is a vanilla connection, false otherwise. + */ + public boolean isVanillaConnection(Connection connection) { + return connection.channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).get() == Boolean.FALSE; + } + + /** + * Indicates whether the server listener has a connection setup that can transmit the given payload id. + * + * @param listener The listener to check. + * @param payloadId The payload id to check. + * @return True if the listener has a connection setup that can transmit the given payload id, false otherwise. + */ + public boolean isConnected(ServerCommonPacketListener listener, ResourceLocation payloadId) { + final NetworkPayloadSetup payloadSetup = listener.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + if (payloadSetup == null) { + LOGGER.warn("Somebody tried to send: {} to a client that has not negotiated with the client. Not sending packet.", payloadId); + return false; + } + + if (listener instanceof ServerConfigurationPacketListener) { + final NetworkChannel channel = payloadSetup.configuration().get(payloadId); + + if (channel == null) { + LOGGER.trace("Somebody tried to send: {} to a client which cannot accept it. Not sending packet.", payloadId); + return false; + } + + return true; + } else if (listener instanceof ServerGamePacketListener) { + final NetworkChannel channel = payloadSetup.play().get(payloadId); + + if (channel == null) { + LOGGER.trace("Somebody tried to send: {} to a client which cannot accept it. Not sending packet.", payloadId); + return false; + } + + return true; + } else { + LOGGER.error("Somebody tried to send: {} to a client that is not in the configuration or play phase. Not sending packet.", payloadId); + throw new IllegalStateException("Somebody tried to send a packet while not in the configuration or play phase. Somebody changed the phases known to NeoForge!"); + } + } + + /** + * Indicates whether the client listener has a connection setup that can transmit the given payload id. + * + * @param listener The listener to check. + * @param payloadId The payload id to check. + * @return True if the listener has a connection setup that can transmit the given payload id, false otherwise. + */ + public boolean isConnected(ClientCommonPacketListener listener, ResourceLocation payloadId) { + final NetworkPayloadSetup payloadSetup = listener.getConnection().channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + if (payloadSetup == null) { + LOGGER.warn("Somebody tried to send: {} to a server that has not negotiated with the client. Not sending packet.", payloadId); + return false; + } + + if (listener instanceof ClientConfigurationPacketListener) { + final NetworkChannel channel = payloadSetup.configuration().get(payloadId); + + if (channel == null) { + LOGGER.trace("Somebody tried to send: {} to a server which cannot accept it. Not sending packet.", payloadId); + return false; + } + + return true; + } else if (listener instanceof ClientGamePacketListener) { + final NetworkChannel channel = payloadSetup.play().get(payloadId); + + if (channel == null) { + LOGGER.trace("Somebody tried to send: {} to a server which cannot accept it. Not sending packet.", payloadId); + return false; + } + + return true; + } else { + LOGGER.error("Somebody tried to send: {} to a server that is not in the configuration or play phase. Not sending packet.", payloadId); + throw new IllegalStateException("Somebody tried to send a packet while not in the configuration or play phase. Somebody changed the phases known to NeoForge!"); + } + } + + /** + * Filters the given packets for a bundle packet in the game phase of the connection. + * + * @param context The context of the connection. + * @param packets The packets to filter. + * @param The type of the listener. + * @return The filtered packets. + */ + public List> filterGameBundlePackets(ChannelHandlerContext context, Iterable> packets) { + final NetworkPayloadSetup payloadSetup = context.channel().attr(ATTRIBUTE_PAYLOAD_SETUP).get(); + if (payloadSetup == null) { + LOGGER.trace("Somebody tried to filter bundled packets to a client that has not negotiated with the server. Not filtering."); + return Lists.newArrayList(packets.iterator()); + } + + final List> toSend = new ArrayList<>(); + packets.forEach(packet -> { + if (!(packet instanceof ClientboundCustomPayloadPacket customPayloadPacket)) { + toSend.add(packet); + return; + } + + if (shouldSendPacketRaw(packet)) { + toSend.add(packet); + return; + } + + final NetworkChannel channel = payloadSetup.play().get(customPayloadPacket.payload().id()); + + if (channel == null) { + LOGGER.trace("Somebody tried to send: {} to a client which cannot accept it. Not sending packet.", customPayloadPacket.payload().id()); + return; + } + + toSend.add(packet); + }); + + return toSend; + } + + /** + * Configures a mock connection. + * + * @param connection The connection to configure. + */ + public void configureMockConnection(final Connection connection) { + connection.channel().attr(ATTRIBUTE_IS_MODDED_CONNECTION).set(true); + connection.channel().attr(ATTRIBUTE_FLOW).set(PacketFlow.SERVERBOUND); + connection.channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(NetworkPayloadSetup.emptyModded()); + + final NetworkPayloadSetup setup = NetworkPayloadSetup.from( + this.knownConfigurationRegistrations.entrySet().stream() + .map(entry -> new NetworkChannel(entry.getKey(), entry.getValue().version())) + .collect(Collectors.toSet()), + this.knownPlayRegistrations.entrySet().stream() + .map(entry -> new NetworkChannel(entry.getKey(), entry.getValue().version())) + .collect(Collectors.toSet())); + + connection.channel().attr(ATTRIBUTE_PAYLOAD_SETUP).set(setup); + + NetworkFilters.injectIfNecessary(connection); + } + + /** + * An {@link ISynchronizedWorkHandler} that can be used to schedule tasks on the main thread of the server or client. This wrapper record is used to line up the APIs of the {@link ReentrantBlockableEventLoop} with the {@link ISynchronizedWorkHandler}. + * + * @param eventLoop The event loop to schedule tasks on. + */ + @SuppressWarnings("resource") + private record EventLoopSynchronizedWorkHandler( + ReentrantBlockableEventLoop eventLoop, T payload) implements ISynchronizedWorkHandler { + + private static final Logger LOGGER = LogUtils.getLogger(); + + /** + * {@inheritDoc} + */ + @Override + public void execute(Runnable task) { + submitAsync(task).exceptionally( + ex -> { + LOGGER.error("Failed to process a synchronized task of the payload: %s".formatted(payload()), ex); + return null; + }); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture submitAsync(Runnable task) { + return eventLoop().submit(task); + } + + /** + * {@inheritDoc} + */ + @Override + public CompletableFuture submitAsync(Supplier task) { + return eventLoop().submit(task); + } + } + + @SuppressWarnings("unchecked") + private record ServerPacketHandler(ServerCommonPacketListener listener) implements IPacketHandler { + + @Override + public void handle(Packet packet) { + resolvePacketGenerics(packet, listener()); + } + + @Override + public void handle(CustomPacketPayload payload) { + handle(new ServerboundCustomPayloadPacket(payload)); + } + + @Override + public void disconnect(Component reason) { + listener().disconnect(reason); + } + + private static void resolvePacketGenerics(Packet packet, ServerCommonPacketListener listener) { + try { + packet.handle((T) listener); + } catch (ClassCastException exception) { + throw new IllegalStateException("Somebody tried to handle a packet in a listener that does not support it.", exception); + } + } + } + + @SuppressWarnings("unchecked") + private record ClientPacketHandler(ClientCommonPacketListener listener) implements IPacketHandler { + + @Override + public void handle(Packet packet) { + resolvePacketGenerics(packet, listener()); + } + + @Override + public void handle(CustomPacketPayload payload) { + handle(new ClientboundCustomPayloadPacket(payload)); + } + + @Override + public void disconnect(Component reason) { + listener().getConnection().disconnect(reason); + } + + private static void resolvePacketGenerics(Packet packet, ClientCommonPacketListener listener) { + try { + packet.handle((T) listener); + } catch (ClassCastException exception) { + throw new IllegalStateException("Somebody tried to handle a packet in a listener that does not support it.", exception); + } + } + } + +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/PayloadHandlerBuilder.java b/src/main/java/net/neoforged/neoforge/network/registration/PayloadHandlerBuilder.java new file mode 100644 index 0000000000..b1ff2824e9 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/PayloadHandlerBuilder.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.function.Consumer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; +import net.neoforged.neoforge.network.handling.IPayloadHandler; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * The internal implementation of {@link IDirectionAwarePayloadHandlerBuilder}, for use by {@link IPayloadRegistrar#common(ResourceLocation, FriendlyByteBuf.Reader, Consumer)} + * + * @param The type of {@link CustomPacketPayload} that this handler handles + */ +@ApiStatus.Internal +class PayloadHandlerBuilder implements IDirectionAwarePayloadHandlerBuilder> { + private @Nullable IPayloadHandler clientSide; + private @Nullable IPayloadHandler serverSide; + + public PayloadHandlerBuilder client(@NotNull IPayloadHandler clientSide) { + this.clientSide = clientSide; + return this; + } + + public PayloadHandlerBuilder server(@NotNull IPayloadHandler serverSide) { + this.serverSide = serverSide; + return this; + } + + /** + * Internal callback method used to configure the play builder with the handlers + * + * @param play The play builder + */ + void handlePlay(IDirectionAwarePayloadHandlerBuilder> play) { + if (clientSide != null) { + play.client(clientSide::handle); + } + if (serverSide != null) { + play.server(serverSide::handle); + } + } + + /** + * Internal callback method used to configure the configuration builder with the handlers + * + * @param configuration The configuration builder + */ + void handleConfiguration(IDirectionAwarePayloadHandlerBuilder> configuration) { + if (clientSide != null) { + configuration.client(clientSide::handle); + } + if (serverSide != null) { + configuration.server(serverSide::handle); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/PlayPayloadHandler.java b/src/main/java/net/neoforged/neoforge/network/registration/PlayPayloadHandler.java new file mode 100644 index 0000000000..ef51218ec1 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/PlayPayloadHandler.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Optional; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * An internal implementation of {@link IDirectionAwarePayloadHandlerBuilder} for {@link IPlayPayloadHandler}. + * + * @param The payload type. + */ +@ApiStatus.Internal +final class PlayPayloadHandler implements IPlayPayloadHandler { + + @Nullable + private final IPlayPayloadHandler clientSide; + @Nullable + private final IPlayPayloadHandler serverSide; + + private PlayPayloadHandler(@Nullable IPlayPayloadHandler clientSide, @Nullable IPlayPayloadHandler serverSide) { + this.clientSide = clientSide; + this.serverSide = serverSide; + } + + @Override + public void handle(T payload, PlayPayloadContext context) { + if (context.flow().isClientbound()) { + if (clientSide != null) { + clientSide.handle(payload, context); + } + } else if (context.flow().isServerbound()) { + if (serverSide != null) { + serverSide.handle(payload, context); + } + } + } + + Optional flow() { + if (clientSide == null && serverSide == null) { + return Optional.empty(); + } + + if (clientSide == null) { + return Optional.of(PacketFlow.SERVERBOUND); + } + + if (serverSide == null) { + return Optional.of(PacketFlow.CLIENTBOUND); + } + + return Optional.empty(); + } + + /** + * A builder for {@link PlayPayloadHandler}. + * + * @param The payload type. + */ + static class Builder implements IDirectionAwarePayloadHandlerBuilder> { + private @Nullable IPlayPayloadHandler clientSide; + private @Nullable IPlayPayloadHandler serverSide; + + public Builder client(@NotNull IPlayPayloadHandler clientSide) { + this.clientSide = clientSide; + return this; + } + + public Builder server(@NotNull IPlayPayloadHandler serverSide) { + this.serverSide = serverSide; + return this; + } + + PlayPayloadHandler create() { + return new PlayPayloadHandler(clientSide, serverSide); + } + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/PlayRegistration.java b/src/main/java/net/neoforged/neoforge/network/registration/PlayRegistration.java new file mode 100644 index 0000000000..21c6350ce6 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/PlayRegistration.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import java.util.Optional; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.neoforged.neoforge.network.handling.IPlayPayloadHandler; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import org.jetbrains.annotations.ApiStatus; + +/** + * A record that holds the information needed to describe a registered play payload, its reader and handler. + * + * @param reader The reader for the payload + * @param handler The handler for the payload + * @param version The version of the payload + * @param flow The flow of the payload + * @param optional Whether the payload is optional + * @param The type of the payload + */ +@ApiStatus.Internal +public record PlayRegistration( + FriendlyByteBuf.Reader reader, + IPlayPayloadHandler handler, + Optional version, + Optional flow, + boolean optional) implements IPlayPayloadHandler, FriendlyByteBuf.Reader { + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void handle(CustomPacketPayload payload, PlayPayloadContext context) { + ((IPlayPayloadHandler) handler).handle(payload, context); + } + + @Override + public CustomPacketPayload apply(FriendlyByteBuf buffer) { + return reader.apply(buffer); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/registration/RegistrationFailedException.java b/src/main/java/net/neoforged/neoforge/network/registration/RegistrationFailedException.java new file mode 100644 index 0000000000..d9eb443d13 --- /dev/null +++ b/src/main/java/net/neoforged/neoforge/network/registration/RegistrationFailedException.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.neoforge.network.registration; + +import net.minecraft.resources.ResourceLocation; +import org.jetbrains.annotations.ApiStatus; + +/** + * Defines an exception that can be thrown at runtime, if a modder has registered a payload incorrectly. + * In practice there are three reasons this exception can be thrown: + *
    + *
  • The payload id is already registered.
  • + *
  • The payload id is registered in the wrong namespace.
  • + *
  • Some other unknown reason.
  • + *
+ * The reason for the exception can be determined by the {@link Reason} enum. + *

+ * Note: this exception is a runtime exception, meaning that it does not need to be caught. + * It is not recommended that this exception is caught, since it is a sign of a programming error. + *

+ */ +public class RegistrationFailedException extends RuntimeException { + + private final ResourceLocation id; + private final String namespace; + private final Reason reason; + + /** + * Creates a new exception with the given parameters. + * The reason can not be unknown. + * + * @param id The id of the payload that was being registered. + * @param namespace The namespace the payload was being registered in. + * @param reason The reason the registration failed. + */ + @ApiStatus.Internal + public RegistrationFailedException(ResourceLocation id, String namespace, Reason reason) { + super(reason.format(id, namespace)); + this.id = id; + this.namespace = namespace; + this.reason = reason; + + if (reason == Reason.UNKNOWN) { + throw new IllegalArgumentException("Reason can not be unknown. Supply a throwing reason for the exception."); + } + } + + /** + * Creates a new exception with the given parameters. + * Automatically sets the reason to unknown, and passes the given throwable to the super constructor as reason for the exception. + * + * @param id The id of the payload that was being registered. + * @param namespace The namespace the payload was being registered in. + * @param throwable The throwable that caused the registration to fail. + */ + @ApiStatus.Internal + public RegistrationFailedException(ResourceLocation id, String namespace, Throwable throwable) { + super(Reason.UNKNOWN.format(id, namespace), throwable); + this.id = id; + this.namespace = namespace; + reason = Reason.UNKNOWN; + } + + /** + * The id of the payload that was being registered. + * + * @return The id of the payload that was being registered. + */ + public ResourceLocation getId() { + return id; + } + + /** + * The namespace the payload should be registered in. + * + * @return The namespace the payload should be registered in. + */ + public String getNamespace() { + return namespace; + } + + /** + * The reason the registration failed. + * + * @return The reason the registration failed. + */ + public Reason getReason() { + return reason; + } + + /** + * Defines possible reasons for a payload registration to fail. + */ + public enum Reason implements ReasonFormatter { + /** + * The payload id is already registered. + */ + DUPLICATE_ID((id, namespace) -> "Duplicate payload id " + id + " for payload in namespace " + namespace + "."), + /** + * The payload id is registered in the wrong namespace. + */ + INVALID_NAMESPACE((id, namespace) -> "Try registering payload in namespace " + namespace + " for payload with id " + id + "."), + /** + * The registrar is invalid. + */ + INVALID_REGISTRAR((id, namespace) -> "Invalid registrar. It can not be used to register payloads."), + /** + * Some other unknown reason, an exception was thrown downstream. + */ + UNKNOWN((id, namespace) -> "General payload registration failure for payload with id " + id + " in namespace " + namespace + "."); + + /** + * The internal formatter used to format the reason. + */ + private final ReasonFormatter formatter; + + Reason(ReasonFormatter formatter) { + this.formatter = formatter; + } + + /** + * {@inheritDoc} + */ + @Override + public String format(ResourceLocation id, String namespace) { + return this.formatter.format(id, namespace); + } + } + + /** + * Internal interface used to format the error message for a given reason. + */ + @FunctionalInterface + private interface ReasonFormatter { + + /** + * Creates a nice error message for the given parameters. + * + * @param id The id of the payload that was being registered. + * @param namespace The namespace the payload was being registered in. + * @return A nice error message for the given parameters. + */ + String format(ResourceLocation id, String namespace); + } +} diff --git a/src/main/java/net/neoforged/neoforge/network/simple/IndexedMessageCodec.java b/src/main/java/net/neoforged/neoforge/network/simple/IndexedMessageCodec.java deleted file mode 100644 index a3d8dac9fc..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/simple/IndexedMessageCodec.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.simple; - -import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; -import it.unimi.dsi.fastutil.shorts.Short2ObjectArrayMap; -import java.util.Objects; -import java.util.Optional; -import net.minecraft.network.FriendlyByteBuf; -import net.neoforged.neoforge.network.HandshakeHandler; -import net.neoforged.neoforge.network.INetworkDirection; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.NetworkHooks; -import net.neoforged.neoforge.network.NetworkInstance; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.apache.logging.log4j.Marker; -import org.apache.logging.log4j.MarkerManager; - -public class IndexedMessageCodec { - private static final Logger LOGGER = LogManager.getLogger(); - private static final Marker SIMPLENET = MarkerManager.getMarker("SIMPLENET"); - private final Short2ObjectArrayMap> indicies = new Short2ObjectArrayMap<>(); - private final Object2ObjectArrayMap, MessageHandler> types = new Object2ObjectArrayMap<>(); - private final NetworkInstance networkInstance; - - public IndexedMessageCodec() { - this(null); - } - - public IndexedMessageCodec(final NetworkInstance instance) { - this.networkInstance = instance; - } - - @SuppressWarnings("unchecked") - public MessageHandler findMessageType(final MSG msgToReply) { - return (MessageHandler) types.get(msgToReply.getClass()); - } - - @SuppressWarnings("unchecked") - MessageHandler findIndex(final short i) { - return (MessageHandler) indicies.get(i); - } - - @SuppressWarnings("OptionalUsedAsFieldOrParameterType") - class MessageHandler { - private final Optional> encoder; - private final Optional> decoder; - private final int index; - private final MessageFunctions.MessageConsumer messageConsumer; - private final Class messageType; - private final Optional> networkDirection; - private Optional> loginIndexSetter; - private Optional> loginIndexGetter; - - public MessageHandler(int index, Class messageType, MessageFunctions.MessageEncoder encoder, MessageFunctions.MessageDecoder decoder, MessageFunctions.MessageConsumer messageConsumer, final Optional> networkDirection) { - this.index = index; - this.messageType = messageType; - this.encoder = Optional.ofNullable(encoder); - this.decoder = Optional.ofNullable(decoder); - this.messageConsumer = messageConsumer; - this.networkDirection = networkDirection; - this.loginIndexGetter = Optional.empty(); - this.loginIndexSetter = Optional.empty(); - indicies.put((short) (index & 0xff), this); - types.put(messageType, this); - } - - void setLoginIndexSetter(MessageFunctions.LoginIndexSetter loginIndexSetter) { - this.loginIndexSetter = Optional.of(loginIndexSetter); - } - - Optional> getLoginIndexSetter() { - return this.loginIndexSetter; - } - - void setLoginIndexGetter(MessageFunctions.LoginIndexGetter loginIndexGetter) { - this.loginIndexGetter = Optional.of(loginIndexGetter); - } - - public Optional> getLoginIndexGetter() { - return this.loginIndexGetter; - } - - MSG newInstance() { - try { - return messageType.newInstance(); - } catch (InstantiationException | IllegalAccessException e) { - LOGGER.error("Invalid login message", e); - throw new RuntimeException(e); - } - } - } - - private static void tryDecode(FriendlyByteBuf payload, NetworkEvent.Context context, int payloadIndex, MessageHandler codec) { - codec.decoder.map(d -> d.decode(payload)).map(p -> { - // Only run the loginIndex function for payloadIndexed packets (login) - if (payloadIndex != Integer.MIN_VALUE) { - codec.getLoginIndexSetter().ifPresent(f -> f.setLoginIndex(p, payloadIndex)); - } - return p; - }).ifPresent(m -> codec.messageConsumer.handle(m, context)); - } - - private static int tryEncode(FriendlyByteBuf target, M message, MessageHandler codec) { - codec.encoder.ifPresent(encoder -> { - target.writeByte(codec.index & 0xff); - encoder.encode(message, target); - }); - return codec.loginIndexGetter.orElse(m -> Integer.MIN_VALUE).getLoginIndex(message); - } - - public int build(MSG message, FriendlyByteBuf target) { - @SuppressWarnings("unchecked") - MessageHandler messageHandler = (MessageHandler) types.get(message.getClass()); - if (messageHandler == null) { - LOGGER.error(SIMPLENET, "Received invalid message {} on channel {}", message.getClass().getName(), Optional.ofNullable(networkInstance).map(NetworkInstance::getChannelName).map(Objects::toString).orElse("MISSING CHANNEL")); - throw new IllegalArgumentException("Invalid message " + message.getClass().getName()); - } - return tryEncode(target, message, messageHandler); - } - - void consume(FriendlyByteBuf payload, int payloadIndex, NetworkEvent.Context context) { - if (payload == null || !payload.isReadable()) { - LOGGER.error(SIMPLENET, "Received empty payload on channel {}", Optional.ofNullable(networkInstance).map(NetworkInstance::getChannelName).map(Objects::toString).orElse("MISSING CHANNEL")); - if (!HandshakeHandler.packetNeedsResponse(context.getNetworkManager(), payloadIndex)) { - context.setPacketHandled(true); //don't disconnect if the corresponding S2C packet that was not recognized on the client doesn't require a proper response - } - return; - } - short discriminator = payload.readUnsignedByte(); - final MessageHandler messageHandler = indicies.get(discriminator); - if (messageHandler == null) { - LOGGER.error(SIMPLENET, "Received invalid discriminator byte {} on channel {}", discriminator, Optional.ofNullable(networkInstance).map(NetworkInstance::getChannelName).map(Objects::toString).orElse("MISSING CHANNEL")); - return; - } - NetworkHooks.validatePacketDirection(context.getDirection(), messageHandler.networkDirection, context.getNetworkManager()); - tryDecode(payload, context, payloadIndex, messageHandler); - } - - MessageHandler addCodecIndex(int index, Class messageType, MessageFunctions.MessageEncoder encoder, MessageFunctions.MessageDecoder decoder, MessageFunctions.MessageConsumer messageConsumer, final Optional> networkDirection) { - return new MessageHandler<>(index, messageType, encoder, decoder, messageConsumer, networkDirection); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/simple/MessageFunctions.java b/src/main/java/net/neoforged/neoforge/network/simple/MessageFunctions.java deleted file mode 100644 index dba8afc83e..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/simple/MessageFunctions.java +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.simple; - -import java.util.List; -import net.minecraft.network.FriendlyByteBuf; -import net.neoforged.neoforge.network.NetworkEvent; - -public final class MessageFunctions { - /** - * Functional interface used to encode messages to a byte buffer. - * - * @param the type of the message that is encoded - */ - @FunctionalInterface - public interface MessageEncoder { - /** - * Encodes the message to the {@code buffer}.
- * This method is usually called as soon as the message is sent, so encoding usually happens on the client/server thread, - * not on the network thread.
- * However, that should not be relied on, and the encoder should try to be thread-safe, and to not interact with the game state. - */ - void encode(MSG message, FriendlyByteBuf buffer); - } - - /** - * Functional interface used to decode messages from a byte buffer. - * - * @param the type of the message that is decoded - */ - @FunctionalInterface - public interface MessageDecoder { - /** - * Decodes the message from the {@code buffer}.
- * This method is called on the network thread, when the packet is received. Do not handle the message in this method. - * - * @return a decoded message - */ - MSG decode(FriendlyByteBuf buffer); - } - - /** - * Functional interface used to handle messages. - * - * @param the type of the message that is handled - */ - @FunctionalInterface - public interface MessageConsumer { - /** - * Handles the message. - * - * @param msg the message to handle - * @param context the context containing the direction, the sender and the connection - */ - void handle(MSG msg, NetworkEvent.Context context); - - default MessageConsumer andThen(MessageConsumer other) { - return (msg, context) -> { - this.handle(msg, context); - other.handle(msg, context); - }; - } - } - - /** - * Functional interface used to generate a list of login packets to be sent to clients. - * - * @param the type of the message that is sent - */ - @FunctionalInterface - public interface LoginPacketGenerator { - List> generate(boolean isLocal); - } - - @FunctionalInterface - public interface LoginIndexGetter { - int getLoginIndex(MSG msg); - } - - @FunctionalInterface - public interface LoginIndexSetter { - void setLoginIndex(MSG msg, int index); - } - - public record LoginPacket(String context, MSG msg) {} -} diff --git a/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java b/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java deleted file mode 100644 index 274bf752db..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/simple/SimpleChannel.java +++ /dev/null @@ -1,368 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.simple; - -import io.netty.buffer.Unpooled; -import java.lang.reflect.InvocationTargetException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Consumer; -import java.util.function.IntSupplier; -import net.minecraft.client.Minecraft; -import net.minecraft.network.Connection; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.protocol.Packet; -import net.neoforged.neoforge.network.INetworkDirection; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.NetworkInstance; -import net.neoforged.neoforge.network.PacketDistributor; -import net.neoforged.neoforge.network.PlayNetworkDirection; -import net.neoforged.neoforge.network.simple.MessageFunctions.LoginIndexGetter; -import net.neoforged.neoforge.network.simple.MessageFunctions.LoginIndexSetter; -import net.neoforged.neoforge.network.simple.MessageFunctions.LoginPacket; -import net.neoforged.neoforge.network.simple.MessageFunctions.LoginPacketGenerator; -import net.neoforged.neoforge.network.simple.MessageFunctions.MessageConsumer; -import net.neoforged.neoforge.network.simple.MessageFunctions.MessageDecoder; -import net.neoforged.neoforge.network.simple.MessageFunctions.MessageEncoder; - -@SuppressWarnings("OptionalUsedAsFieldOrParameterType") -public class SimpleChannel { - private final NetworkInstance instance; - private final IndexedMessageCodec indexedCodec; - private final Optional> registryChangeConsumer; - - private record LoginPacketEntry(LoginPacketGenerator generator, boolean needsResponse) {} - - private final List loginPackets = new ArrayList<>(); - - public SimpleChannel(NetworkInstance instance) { - this(instance, Optional.empty()); - } - - private SimpleChannel(NetworkInstance instance, Optional> registryChangeNotify) { - this.instance = instance; - this.indexedCodec = new IndexedMessageCodec(instance); - instance.addListener(this::networkEventListener); - instance.addGatherListener(this::networkLoginGather); - this.registryChangeConsumer = registryChangeNotify; - } - - public SimpleChannel(NetworkInstance instance, Consumer registryChangeNotify) { - this(instance, Optional.of(registryChangeNotify)); - } - - private void networkLoginGather(final NetworkEvent.GatherLoginPayloadsEvent gatherEvent) { - loginPackets.forEach(packetGenerator -> packetGenerator.generator.generate(gatherEvent.isLocal()).forEach(p -> { - FriendlyByteBuf pb = new FriendlyByteBuf(Unpooled.buffer()); - this.indexedCodec.build(p.msg(), pb); - gatherEvent.add(pb, this.instance.getChannelName(), p.context(), packetGenerator.needsResponse); - })); - } - - private void networkEventListener(final NetworkEvent networkEvent) { - if (networkEvent instanceof NetworkEvent.ChannelRegistrationChangeEvent) { - this.registryChangeConsumer.ifPresent(l -> l.accept(((NetworkEvent.ChannelRegistrationChangeEvent) networkEvent))); - } else { - this.indexedCodec.consume(networkEvent.getPayload(), networkEvent.getLoginIndex(), networkEvent.getSource()); - } - } - - public int encodeMessage(MSG message, final FriendlyByteBuf target) { - return this.indexedCodec.build(message, target); - } - - public IndexedMessageCodec.MessageHandler registerMessage(int index, Class messageType, MessageFunctions.MessageEncoder encoder, MessageFunctions.MessageDecoder decoder, MessageFunctions.MessageConsumer messageConsumer) { - return registerMessage(index, messageType, encoder, decoder, messageConsumer, Optional.empty()); - } - - public IndexedMessageCodec.MessageHandler registerMessage(int index, Class messageType, MessageFunctions.MessageEncoder encoder, MessageFunctions.MessageDecoder decoder, MessageFunctions.MessageConsumer messageConsumer, final Optional> networkDirection) { - return this.indexedCodec.addCodecIndex(index, messageType, encoder, decoder, messageConsumer, networkDirection); - } - - private INetworkDirection.PacketData toBuffer(MSG msg) { - final FriendlyByteBuf bufIn = new FriendlyByteBuf(Unpooled.buffer()); - int index = encodeMessage(msg, bufIn); - return new INetworkDirection.PacketData(bufIn, index); - } - - public void sendToServer(MSG message) { - sendTo(message, Minecraft.getInstance().getConnection().getConnection(), PlayNetworkDirection.PLAY_TO_SERVER); - } - - public void sendTo(MSG message, Connection manager, PlayNetworkDirection direction) { - manager.send(toVanillaPacket(message, direction)); - } - - /** - * Send a message to the {@link PacketDistributor.PacketTarget} from a {@link PacketDistributor} instance. - * - *
-     * channel.send(PacketDistributor.PLAYER.with(() -> player), message)
-     * 
- * - * @param target The curried target from a PacketDistributor - * @param message The message to send - * @param The type of the message - */ - public void send(PacketDistributor.PacketTarget target, MSG message) { - target.send(toVanillaPacket(message, target.getDirection())); - } - - public Packet toVanillaPacket(MSG message, PlayNetworkDirection direction) { - return direction.buildPacket(toBuffer(message), instance.getChannelName()); - } - - public void reply(MSG msgToReply, NetworkEvent.Context context) { - context.getPacketDispatcher().sendPacket(instance.getChannelName(), toBuffer(msgToReply).buffer()); - } - - /** - * {@return {@code true} if the channel is present in the given connection} - */ - public boolean isRemotePresent(Connection manager) { - return instance.isRemotePresent(manager); - } - - /** - * Build a new MessageBuilder. The type should implement {@link java.util.function.IntSupplier} if it is a login - * packet. - * - * @param type Type of message - * @param id id in the indexed codec - * @param Type of type - * @return a MessageBuilder - */ - public MessageBuilder messageBuilder(final Class type, int id) { - return MessageBuilder.forType(this, type, id, null); - } - - /** - * Build a new MessageBuilder. The type should implement {@link java.util.function.IntSupplier} if it is a login - * packet. - * - * @param type Type of message - * @param id id in the indexed codec - * @param direction a impl direction which will be asserted before any processing of this message occurs. Use to - * enforce strict sided handling to prevent spoofing. - * @param Type of type - * @return a MessageBuilder - */ - public MessageBuilder messageBuilder(final Class type, int id, INetworkDirection direction) { - return MessageBuilder.forType(this, type, id, direction); - } - - /** - * Build a new MessageBuilder for a message that implements {@link SimpleMessage}. This will automatically set the - * {@link MessageBuilder#encoder(MessageEncoder) encoder} and the handler. - * - * @param type the type of message - * @param id the id in the indexed codec - * @param the type of the message - * @return a MessageBuilder - */ - public MessageBuilder simpleMessageBuilder(final Class type, int id) { - return simpleMessageBuilder(type, id, null); - } - - /** - * Build a new MessageBuilder for a message that implements {@link SimpleMessage}. This will automatically set the - * {@link MessageBuilder#encoder(MessageEncoder) encoder} and the handler. - * - * @param type the type of message - * @param id the id in the indexed codec - * @param direction a impl direction which will be asserted before any processing of this message occurs. Use to - * enforce strict sided handling to prevent spoofing. - * @param the type of the message - * @return a MessageBuilder - */ - public MessageBuilder simpleMessageBuilder(final Class type, int id, INetworkDirection direction) { - return messageBuilder(type, id, direction) - .consumerNetworkThread((msg, context) -> { - msg.handleNetworkThread(context); - context.enqueueWork(() -> msg.handleMainThread(context)); - }) - .encoder(SimpleMessage::encode); - } - - /** - * Build a new MessageBuilder for a login message that implements {@link SimpleLoginMessage}. This will automatically set the - * {@link MessageBuilder#encoder(MessageEncoder) encoder}, the handler and the {@link MessageBuilder#loginIndex(LoginIndexGetter, LoginIndexSetter) login index setter and getter}. - * - * @param type the type of message - * @param id the id in the indexed codec - * @param the type of the message - * @return a MessageBuilder - */ - public MessageBuilder simpleLoginMessageBuilder(final Class type, int id) { - return simpleLoginMessageBuilder(type, id, null); - } - - /** - * Build a new MessageBuilder for a login message that implements {@link SimpleLoginMessage}. This will automatically set the - * {@link MessageBuilder#encoder(MessageEncoder) encoder}, the handler and the {@link MessageBuilder#loginIndex(LoginIndexGetter, LoginIndexSetter) login index setter and getter}. - * - * @param type the type of message - * @param id the id in the indexed codec - * @param direction a impl direction which will be asserted before any processing of this message occurs. Use to - * enforce strict sided handling to prevent spoofing. - * @param the type of the message - * @return a MessageBuilder - */ - public MessageBuilder simpleLoginMessageBuilder(final Class type, int id, INetworkDirection direction) { - return simpleMessageBuilder(type, id, direction) - .loginIndex(SimpleLoginMessage::getLoginIndex, SimpleLoginMessage::setLoginIndex); - } - - public static class MessageBuilder { - private SimpleChannel channel; - private Class type; - private int id; - private MessageEncoder encoder; - private MessageDecoder decoder; - private MessageConsumer consumer; - private LoginIndexGetter loginIndexGetter; - private LoginIndexSetter loginIndexSetter; - private LoginPacketGenerator loginPacketGenerators; - private Optional> networkDirection; - private boolean needsResponse = true; - - private static MessageBuilder forType(final SimpleChannel channel, final Class type, int id, INetworkDirection playNetworkDirection) { - MessageBuilder builder = new MessageBuilder<>(); - builder.channel = channel; - builder.id = id; - builder.type = type; - builder.networkDirection = Optional.ofNullable(playNetworkDirection); - return builder; - } - - /** - * Set the message encoder, which writes this message to a {@link FriendlyByteBuf}. - *

- * The encoder is called immediately {@linkplain #send(PacketDistributor.PacketTarget, Object) when the - * packet is sent}. This means encoding typically occurs on the main server/client thread rather than on the - * network thread. - *

- * However, this behaviour should not be relied on, and the encoder should try to be thread-safe and not - * interact with the current game state. - * - * @param encoder The message encoder. - * @return This message builder, for chaining. - */ - public MessageBuilder encoder(MessageEncoder encoder) { - this.encoder = encoder; - return this; - } - - /** - * Set the message decoder, which reads the message from a {@link FriendlyByteBuf}. - *

- * The decoder is called when the message is received on the network thread. The decoder should not attempt to - * access or mutate any game state, deferring that until the {@linkplain #consumerMainThread(MessageConsumer)} the - * message is handled}. - * - * @param decoder The message decoder. - * @return The message builder, for chaining. - */ - public MessageBuilder decoder(MessageDecoder decoder) { - this.decoder = decoder; - return this; - } - - public MessageBuilder loginIndex(LoginIndexGetter loginIndexGetter, LoginIndexSetter loginIndexSetter) { - this.loginIndexGetter = loginIndexGetter; - this.loginIndexSetter = loginIndexSetter; - return this; - } - - public MessageBuilder buildLoginPacketList(LoginPacketGenerator loginPacketGenerators) { - this.loginPacketGenerators = loginPacketGenerators; - return this; - } - - public MessageBuilder markAsLoginPacket() { - this.loginPacketGenerators = (isLocal) -> { - try { - return Collections.singletonList(new LoginPacket<>(type.getName(), type.getConstructor().newInstance())); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchMethodException e) { - throw new RuntimeException("Inaccessible no-arg constructor for message " + type.getName(), e); - } - }; - return this; - } - - /** - * Marks this packet as not needing a response when sent to the client - */ - public MessageBuilder noResponse() { - this.needsResponse = false; - return this; - } - - /** - * Set the message consumer, which is called once a message has been decoded. This accepts the decoded message - * object and the message's context. - *

- * The consumer is called on the network thread, and so should not interact with most game state by default. - * {@link NetworkEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or - * client thread. Alternatively one can use {@link #consumerMainThread(MessageConsumer)} to run the handler on the - * main thread. - *

- * The packet is marked as {@link NetworkEvent.Context#setPacketHandled(boolean) handled}. You may manually revert that state if you wish to, but generally - * you do not want to, as a packet that is not handled will continue to try to find handlers. - * - * @param consumer The message consumer. - * @return The message builder, for chaining. - * @see #consumerMainThread(MessageConsumer) - */ - public MessageBuilder consumerNetworkThread(MessageConsumer consumer) { - this.consumer = consumer; - return this; - } - - /** - * Set the message consumer, which is called once a message has been decoded. This accepts the decoded message - * object and the message's context. - *

- * Unlike {@link #consumerNetworkThread(MessageConsumer)}, the consumer is called on the main thread, and so can - * interact with most game state by default. - *

- * The packet is marked as {@link NetworkEvent.Context#setPacketHandled(boolean) handled}. You may manually revert that state if you wish to, but generally - * you do not want to, as a packet that is not handled will continue to try to find handlers. - * - * @param consumer The message consumer. - * @return The message builder, for chaining. - * @see #consumerNetworkThread(MessageConsumer) - */ - public MessageBuilder consumerMainThread(MessageConsumer consumer) { - this.consumer = (msg, context) -> context.enqueueWork(() -> consumer.handle(msg, context)); - return this; - } - - public void add() { - Objects.requireNonNull(this.consumer, () -> "Message of type " + this.type.getName() + " is missing a handler!"); - - final IndexedMessageCodec.MessageHandler message = this.channel.registerMessage(this.id, this.type, this.encoder, this.decoder, (msg, context) -> { - context.setPacketHandled(true); // Mark as handled by default, people can mark it as not handled manually - this.consumer.handle(msg, context); - }, this.networkDirection); - if (this.loginIndexSetter != null) { - message.setLoginIndexSetter(this.loginIndexSetter); - } - if (this.loginIndexGetter != null) { - if (!IntSupplier.class.isAssignableFrom(this.type)) { - throw new IllegalArgumentException("Login packet type that does not supply an index as an IntSupplier"); - } - message.setLoginIndexGetter(this.loginIndexGetter); - } - if (this.loginPacketGenerators != null) { - this.channel.loginPackets.add(new LoginPacketEntry(this.loginPacketGenerators, this.needsResponse)); - } - } - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/simple/SimpleLoginMessage.java b/src/main/java/net/neoforged/neoforge/network/simple/SimpleLoginMessage.java deleted file mode 100644 index 3f5a66c3df..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/simple/SimpleLoginMessage.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.simple; - -import java.util.function.IntSupplier; - -/** - * An abstraction for login network packets. - * - * @see SimpleChannel#simpleLoginMessageBuilder(Class, int) - */ -public interface SimpleLoginMessage extends SimpleMessage, IntSupplier { - /** - * {@return the index of this packet} - */ - int getLoginIndex(); - - /** - * Sets the index of this packet. - * - * @param loginIndex the index of the packet - */ - void setLoginIndex(int loginIndex); - - @Override - default int getAsInt() { - return getLoginIndex(); - } -} diff --git a/src/main/java/net/neoforged/neoforge/network/simple/SimpleMessage.java b/src/main/java/net/neoforged/neoforge/network/simple/SimpleMessage.java deleted file mode 100644 index f2599948b3..0000000000 --- a/src/main/java/net/neoforged/neoforge/network/simple/SimpleMessage.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.neoforge.network.simple; - -import net.minecraft.network.FriendlyByteBuf; -import net.neoforged.neoforge.network.NetworkEvent; - -/** - * An abstraction for network packets. - * - * @see SimpleChannel#simpleMessageBuilder(Class, int) - */ -public interface SimpleMessage { - /** - * Encodes this message to the {@code buffer}. - * - * @see MessageFunctions.MessageEncoder - */ - void encode(FriendlyByteBuf buffer); - - /** - * Called on the server/client thread, in order to handle this packet. Accessing game state is safe here. - * - * @param context the network context - */ - default void handleMainThread(NetworkEvent.Context context) {} - - /** - * Called on the network thread, in order to handle this packet. Accessing game state is not safe here, - * as the server is usually not thread-safe. - * Seriously, do NOT access the game state in this method. - * - * @param context the network context - */ - default void handleNetworkThread(NetworkEvent.Context context) {} -} diff --git a/src/main/java/net/neoforged/neoforge/registries/RegistryManager.java b/src/main/java/net/neoforged/neoforge/registries/RegistryManager.java index 9622655fd3..c8f5698f90 100644 --- a/src/main/java/net/neoforged/neoforge/registries/RegistryManager.java +++ b/src/main/java/net/neoforged/neoforge/registries/RegistryManager.java @@ -20,8 +20,7 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.neoforged.fml.ModLoader; -import net.neoforged.neoforge.network.HandshakeMessages; -import net.neoforged.neoforge.network.simple.MessageFunctions; +import net.neoforged.neoforge.network.payload.FrozenRegistryPayload; import org.slf4j.Logger; import org.slf4j.Marker; import org.slf4j.MarkerFactory; @@ -199,12 +198,12 @@ public static Map takeSnapshot(SnapshotType return map; } - public static List> generateRegistryPackets(boolean isLocal) { + public static List generateRegistryPackets(boolean isLocal) { if (isLocal) return List.of(); return takeSnapshot(SnapshotType.SYNC_TO_CLIENT).entrySet().stream() - .map(e -> new MessageFunctions.LoginPacket<>("Registry " + e.getKey(), new HandshakeMessages.S2CRegistry(e.getKey(), e.getValue()))) + .map(e -> new FrozenRegistryPayload(e.getKey(), e.getValue())) .toList(); } diff --git a/src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java b/src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java index 06d6d5512d..9cbf723af2 100644 --- a/src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java +++ b/src/main/java/net/neoforged/neoforge/registries/RegistrySnapshot.java @@ -31,7 +31,7 @@ public class RegistrySnapshot { @Nullable private final Registry fullBackup; @Nullable - private FriendlyByteBuf binary = null; + private byte[] binary = null; /** * Creates a blank snapshot to populate. @@ -43,7 +43,7 @@ private RegistrySnapshot() { /** * Creates a registry snapshot based on the given registry. * - * @param registry the registry to snapshot + * @param registry the registry to snapshot. * @param full if {@code true}, all entries will be stored in this snapshot. * These entries are never saved to disk nor sent to the client. * @param the registry type @@ -66,6 +66,37 @@ public RegistrySnapshot(Registry registry, boolean full) { } } + /** + * Creates a registry snapshot from the received buffer. + * + * @param buf the buffer containing the data of the received snapshot. + */ + public RegistrySnapshot(FriendlyByteBuf buf) { + this(); + buf.readMap(size -> this.ids, FriendlyByteBuf::readVarInt, FriendlyByteBuf::readResourceLocation); + buf.readMap(size -> this.aliases, FriendlyByteBuf::readResourceLocation, FriendlyByteBuf::readResourceLocation); + } + + /** + * Write the registry snapshot to the given buffer and cache the binary data. + * + * @param buf the buffer to write to. + */ + public synchronized void write(FriendlyByteBuf buf) { + if (this.binary == null) { + FriendlyByteBuf pkt = new FriendlyByteBuf(Unpooled.buffer()); + try { + pkt.writeMap(this.ids, FriendlyByteBuf::writeVarInt, FriendlyByteBuf::writeResourceLocation); + pkt.writeMap(this.aliases, FriendlyByteBuf::writeResourceLocation, FriendlyByteBuf::writeResourceLocation); + this.binary = new byte[pkt.readableBytes()]; + pkt.readBytes(this.binary); + } finally { + pkt.release(); + } + } + buf.writeBytes(this.binary); + } + public Int2ObjectSortedMap getIds() { return this.idsView; } @@ -79,43 +110,4 @@ public Map getAliases() { public Registry getFullBackup() { return (Registry) this.fullBackup; } - - public synchronized FriendlyByteBuf getPacketData() { - if (this.binary == null) { - FriendlyByteBuf pkt = new FriendlyByteBuf(Unpooled.buffer()); - - pkt.writeVarInt(this.ids.size()); - this.ids.forEach((k, v) -> { - pkt.writeVarInt(k); - pkt.writeResourceLocation(v); - }); - - pkt.writeVarInt(this.aliases.size()); - this.aliases.forEach((k, v) -> { - pkt.writeResourceLocation(k); - pkt.writeResourceLocation(v); - }); - - this.binary = pkt; - } - - return new FriendlyByteBuf(this.binary.slice()); - } - - public static RegistrySnapshot read(@Nullable FriendlyByteBuf buf) { - if (buf == null) - return new RegistrySnapshot(); - - RegistrySnapshot ret = new RegistrySnapshot(); - - int len = buf.readVarInt(); - for (int x = 0; x < len; x++) - ret.ids.put(buf.readVarInt(), buf.readResourceLocation()); - - len = buf.readVarInt(); - for (int x = 0; x < len; x++) - ret.aliases.put(buf.readResourceLocation(), buf.readResourceLocation()); - - return ret; - } } diff --git a/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java b/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java index ee435b5072..6e77bc8a3e 100644 --- a/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java +++ b/src/main/java/net/neoforged/neoforge/server/ServerLifecycleHooks.java @@ -11,17 +11,10 @@ import java.nio.file.Path; import java.util.List; import java.util.concurrent.CountDownLatch; -import java.util.concurrent.atomic.AtomicBoolean; import net.minecraft.core.Holder; import net.minecraft.core.RegistryAccess; import net.minecraft.core.registries.Registries; import net.minecraft.gametest.framework.GameTestServer; -import net.minecraft.network.Connection; -import net.minecraft.network.chat.Component; -import net.minecraft.network.chat.MutableComponent; -import net.minecraft.network.protocol.handshake.ClientIntent; -import net.minecraft.network.protocol.handshake.ClientIntentionPacket; -import net.minecraft.network.protocol.login.ClientboundLoginDisconnectPacket; import net.minecraft.server.MinecraftServer; import net.minecraft.world.level.storage.LevelResource; import net.neoforged.fml.config.ConfigTracker; @@ -38,10 +31,6 @@ import net.neoforged.neoforge.event.server.ServerStoppedEvent; import net.neoforged.neoforge.event.server.ServerStoppingEvent; import net.neoforged.neoforge.gametest.GameTestHooks; -import net.neoforged.neoforge.network.ConnectionType; -import net.neoforged.neoforge.network.NetworkConstants; -import net.neoforged.neoforge.network.NetworkHooks; -import net.neoforged.neoforge.network.NetworkRegistry; import net.neoforged.neoforge.registries.NeoForgeRegistries; import net.neoforged.neoforge.registries.NeoForgeRegistries.Keys; import net.neoforged.neoforge.registries.RegistryManager; @@ -104,11 +93,9 @@ public static void handleServerStarting(final MinecraftServer server) { public static void handleServerStarted(final MinecraftServer server) { NeoForge.EVENT_BUS.post(new ServerStartedEvent(server)); - allowLogins.set(true); } public static void handleServerStopping(final MinecraftServer server) { - allowLogins.set(false); NeoForge.EVENT_BUS.post(new ServerStoppingEvent(server)); } @@ -134,50 +121,6 @@ public static MinecraftServer getCurrentServer() { return currentServer; } - private static AtomicBoolean allowLogins = new AtomicBoolean(false); - - public static boolean handleServerLogin(final ClientIntentionPacket packet, final Connection manager) { - if (!allowLogins.get()) { - MutableComponent text = Component.literal("Server is still starting! Please wait before reconnecting."); - LOGGER.info(SERVERHOOKS, "Disconnecting Player (server is still starting): {}", text.getContents()); - manager.send(new ClientboundLoginDisconnectPacket(text)); - manager.disconnect(text); - return false; - } - - if (packet.intention() == ClientIntent.LOGIN) { - final ConnectionType connectionType = ConnectionType.forVersionFlag(packet.getFMLVersion()); - final int versionNumber = connectionType.getFMLVersionNumber(packet.getFMLVersion()); - - if (connectionType == ConnectionType.MODDED && versionNumber != NetworkConstants.FMLNETVERSION) { - rejectConnection(manager, connectionType, "This modded server is not impl compatible with your modded client. Please verify your NeoForge version closely matches the server. Got net version " + versionNumber + " this server is net version " + NetworkConstants.FMLNETVERSION); - return false; - } - - if (connectionType == ConnectionType.VANILLA && !NetworkRegistry.acceptsVanillaClientConnections()) { - rejectConnection(manager, connectionType, "This server has mods that require NeoForge to be installed on the client. Contact your server admin for more details."); - return false; - } - } - - if (packet.intention() == ClientIntent.STATUS) return true; - - NetworkHooks.registerServerLoginChannel(manager, packet); - return true; - - } - - private static void rejectConnection(final Connection manager, ConnectionType type, String message) { - manager.setClientboundProtocolAfterHandshake(ClientIntent.LOGIN); - String ip = "local"; - if (manager.getRemoteAddress() != null) - ip = manager.getRemoteAddress().toString(); - LOGGER.info(SERVERHOOKS, "[{}] Disconnecting {} connection attempt: {}", ip, type, message); - MutableComponent text = Component.literal(message); - manager.send(new ClientboundLoginDisconnectPacket(text)); - manager.disconnect(text); - } - public static void handleExit(int retVal) { System.exit(retVal); } diff --git a/src/main/java/net/neoforged/neoforge/server/command/TextComponentHelper.java b/src/main/java/net/neoforged/neoforge/server/command/TextComponentHelper.java index 0996778013..ea80c6765f 100644 --- a/src/main/java/net/neoforged/neoforge/server/command/TextComponentHelper.java +++ b/src/main/java/net/neoforged/neoforge/server/command/TextComponentHelper.java @@ -12,8 +12,7 @@ import net.minecraft.network.chat.MutableComponent; import net.minecraft.server.level.ServerPlayer; import net.minecraft.server.network.ServerGamePacketListenerImpl; -import net.neoforged.neoforge.network.ConnectionType; -import net.neoforged.neoforge.network.NetworkHooks; +import net.neoforged.neoforge.network.registration.NetworkRegistry; public class TextComponentHelper { private TextComponentHelper() {} @@ -30,10 +29,9 @@ public static MutableComponent createComponentTranslation(CommandSource source, } private static boolean isVanillaClient(CommandSource sender) { - if (sender instanceof ServerPlayer) { - ServerPlayer playerMP = (ServerPlayer) sender; + if (sender instanceof ServerPlayer playerMP) { ServerGamePacketListenerImpl channel = playerMP.connection; - return NetworkHooks.getConnectionType(() -> channel.connection) == ConnectionType.VANILLA; + return NetworkRegistry.getInstance().isVanillaConnection(channel.getConnection()); } return false; } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 044faec357..e6c75a2ff8 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -526,3 +526,6 @@ protected net.minecraft.world.level.portal.PortalForcer level # level public net.minecraft.world.level.storage.LevelResource (Ljava/lang/String;)V # constructor private-f net.minecraft.world.level.storage.loot.LootPool rolls # rolls private-f net.minecraft.world.level.storage.loot.LootPool bonusRolls # bonusRolls +public net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket KNOWN_TYPES +public net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket KNOWN_TYPES +public net.minecraft.server.network.ServerConfigurationPacketListenerImpl finishCurrentTask(Lnet/minecraft/server/network/ConfigurationTask$Type;)V diff --git a/src/main/resources/assets/neoforge/lang/en_us.json b/src/main/resources/assets/neoforge/lang/en_us.json index 036d13c85c..e8dfbc2cbe 100644 --- a/src/main/resources/assets/neoforge/lang/en_us.json +++ b/src/main/resources/assets/neoforge/lang/en_us.json @@ -50,12 +50,14 @@ "fml.modmismatchscreen.missingmods.client": "Your client is missing the following mods, install these mods to join this server:", "fml.modmismatchscreen.missingmods.server": "The server is missing the following mods, remove these mods from your client to join this server:", "fml.modmismatchscreen.mismatchedmods": "The following mod versions do not match, install the same version of these mods that the server has to join this server:", - "fml.modmismatchscreen.table.modname": "Mod name", + "fml.modmismatchscreen.table.channelname": "Channel name", "fml.modmismatchscreen.table.youneed": "You need", "fml.modmismatchscreen.table.youhave": "You have", "fml.modmismatchscreen.table.serverhas": "Server has", "fml.modmismatchscreen.additional": "[{0} additional, see latest.log for the full list]", "fml.modmismatchscreen.homepage": "Click to get to the homepage of this mod", + "fml.modmismatchscreen.table.reason": "Reason", + "fml.modmismatchscreen.table.visit.mod_page": "Open the mod page of the mod that registers the channel: %s", "fml.language.missingversion": "Mod File {5} needs language provider {3}:{4,vr} to load\n\u00a77We have found {6,i18n,fml.messages.artifactversion}", "fml.modloading.missingclasses": "The Mod File {3} has mods that were not found", "fml.modloading.missingmetadata": "mods.toml missing metadata for modid {3}", @@ -235,5 +237,22 @@ "neoforge.chatType.system": "{0}", - "pack.neoforge.description": "NeoForge data/resource pack" + "pack.neoforge.description": "NeoForge data/resource pack", + + "neoforge.network.negotiation.failure.missing.client.server": "This channel is missing on the server side, but required on the client!", + "neoforge.network.negotiation.failure.missing.server.client": "This channel is missing on the client side, but required on the server!", + "neoforge.network.negotiation.failure.flow.client.missing": "The client wants the payload to flow: %s, but the server does not support it!", + "neoforge.network.negotiation.failure.flow.server.missing": "The server wants the payload to flow: %s, but the client does not support it!", + "neoforge.network.negotiation.failure.flow.client.mismatch": "The client wants the payload to flow: %s, but the server wants it to flow: %s!", + "neoforge.network.negotiation.failure.flow.server.mismatch": "The server wants the payload to flow: %s, but the client wants it to flow: %s!", + "neoforge.network.negotiation.failure.version.client.missing": "The client wants the payload to be version: %s, but the server does not support it!", + "neoforge.network.negotiation.failure.version.server.missing": "The server wants the payload to be version: %s, but the client does not support it!", + "neoforge.network.negotiation.failure.version.mismatch": "The client wants the payload to be version: %s, but the server wants it to be version: %s!", + "neoforge.network.invalid_flow": "Failed to process a payload that was send with an invalid flow: %s", + "neoforge.network.negotiation.failure.vanilla.client.not_supported": "You are trying to connect to a server that is running NeoForge, but you are not. Please install NeoForge Version: %s to connect to this server.", + "neoforge.network.advanced_add_entity.failed": "Failed to process advanced entity spawn data: %s", + "neoforge.network.advanced_open_screen.failed": "Failed to open a screen with advanced data: %s", + "neoforge.network.registries.sync.missing": "Not all expected registries were received from the server! (missing: %s)", + "neoforge.network.registries.sync.server-with-unknown-keys": "The server send registries with unknown keys: %s", + "neoforge.network.registries.sync.failed": "Failed to sync registries from the server: %s" } diff --git a/testframework/src/main/java/net/neoforged/testframework/conf/FrameworkConfiguration.java b/testframework/src/main/java/net/neoforged/testframework/conf/FrameworkConfiguration.java index b4a6c6225e..6c9b5b6f74 100644 --- a/testframework/src/main/java/net/neoforged/testframework/conf/FrameworkConfiguration.java +++ b/testframework/src/main/java/net/neoforged/testframework/conf/FrameworkConfiguration.java @@ -14,8 +14,6 @@ import net.minecraft.MethodsReturnNonnullByDefault; import net.minecraft.commands.Commands; import net.minecraft.resources.ResourceLocation; -import net.neoforged.neoforge.network.NetworkRegistry; -import net.neoforged.neoforge.network.simple.SimpleChannel; import net.neoforged.testframework.impl.MutableTestFramework; import net.neoforged.testframework.impl.TestFrameworkImpl; import org.jetbrains.annotations.Nullable; @@ -24,7 +22,7 @@ @MethodsReturnNonnullByDefault public record FrameworkConfiguration( ResourceLocation id, Collection enabledFeatures, int commandRequiredPermission, - SimpleChannel networkingChannel, List enabledTests, @Nullable Supplier clientConfiguration) { + List enabledTests, @Nullable Supplier clientConfiguration) { public static Builder builder(ResourceLocation id) { return new Builder(id); @@ -43,7 +41,6 @@ public static final class Builder { private final Collection features = EnumSet.noneOf(Feature.class); private int commandRequiredPermission = Commands.LEVEL_GAMEMASTERS; - private @Nullable SimpleChannel networkingChannel; private final List enabledTests = new ArrayList<>(); private @Nullable Supplier clientConfiguration; @@ -76,25 +73,15 @@ public Builder enableTests(String... tests) { return this; } - public Builder networkingChannel(SimpleChannel channel) { - this.networkingChannel = channel; - return this; - } - public Builder clientConfiguration(Supplier clientConfiguration) { this.clientConfiguration = clientConfiguration; return this; } public FrameworkConfiguration build() { - final SimpleChannel channel = networkingChannel == null ? NetworkRegistry.ChannelBuilder.named(id) - .clientAcceptedVersions(e -> true) - .serverAcceptedVersions(e -> true) - .networkProtocolVersion(() -> "yes") - .simpleChannel() : networkingChannel; return new FrameworkConfiguration( id, features, commandRequiredPermission, - channel, enabledTests, clientConfiguration); + enabledTests, clientConfiguration); } } } diff --git a/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java b/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java index 3aa8d6a16b..7716c23702 100644 --- a/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java +++ b/testframework/src/main/java/net/neoforged/testframework/gametest/ExtendedGameTestHelper.java @@ -51,6 +51,7 @@ import net.neoforged.bus.api.Event; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.entity.living.LivingKnockBackEvent; +import net.neoforged.neoforge.network.registration.NetworkRegistry; import org.jetbrains.annotations.Nullable; public class ExtendedGameTestHelper extends GameTestHelper { @@ -111,14 +112,23 @@ public void tick() { super.tick(); serverplayer.resetLastActionTime(); } + + @Override + public boolean isMemoryConnection() { + return true; + } }; EmbeddedChannel embeddedchannel = new EmbeddedChannel(connection); embeddedchannel.attr(Connection.ATTRIBUTE_SERVERBOUND_PROTOCOL).set(ConnectionProtocol.PLAY.codec(PacketFlow.SERVERBOUND)); + embeddedchannel.attr(Connection.ATTRIBUTE_CLIENTBOUND_PROTOCOL).set(ConnectionProtocol.PLAY.codec(PacketFlow.CLIENTBOUND)); + NetworkRegistry.getInstance().configureMockConnection(connection); this.getLevel().getServer().getPlayerList().placeNewPlayer(connection, serverplayer, commonlistenercookie); this.getLevel().getServer().getConnection().getConnections().add(connection); this.testInfo.addListener(serverplayer); serverplayer.gameMode.changeGameModeForPlayer(gameType); serverplayer.setYRot(180); + serverplayer.connection.chunkSender.sendNextChunks(serverplayer); + serverplayer.connection.chunkSender.onChunkBatchReceivedByClient(64f); return serverplayer; } diff --git a/testframework/src/main/java/net/neoforged/testframework/gametest/GameTestPlayer.java b/testframework/src/main/java/net/neoforged/testframework/gametest/GameTestPlayer.java index a636189b45..372dbbe744 100644 --- a/testframework/src/main/java/net/neoforged/testframework/gametest/GameTestPlayer.java +++ b/testframework/src/main/java/net/neoforged/testframework/gametest/GameTestPlayer.java @@ -10,7 +10,9 @@ import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; +import java.util.function.Function; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import net.minecraft.core.BlockPos; import net.minecraft.gametest.framework.GameTestHelper; import net.minecraft.gametest.framework.GameTestInfo; @@ -18,6 +20,9 @@ import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.common.ClientCommonPacketListener; +import net.minecraft.network.protocol.common.ClientboundCustomPayloadPacket; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.game.ClientboundBundlePacket; import net.minecraft.server.MinecraftServer; import net.minecraft.server.level.ClientInformation; import net.minecraft.server.level.ServerLevel; @@ -34,6 +39,14 @@ public GameTestPlayer(MinecraftServer server, ServerLevel level, GameProfile pro this.helper = helper; } + @Override + public void moveTo(double x, double y, double z) { + super.moveTo(x, y, z); + this.serverLevel().getChunkSource().move(this); //We need to move the player to the correct chunk + this.connection.chunkSender.sendNextChunks(this); //And send the chunks to the player + this.connection.chunkSender.onChunkBatchReceivedByClient(64f); //Also mark them as received. + } + public GameTestPlayer moveToCorner() { moveTo(helper.absoluteVec(new BlockPos(0, helper.testInfo.getStructureName().endsWith("_floor") ? 2 : 1, 0).getCenter()).subtract(0, 0.5, 0)); return this; @@ -79,8 +92,26 @@ private void disconnectGameTest() { this.listeners.clear(); } - public > Stream getOutboundPackets(Class type) { + @SuppressWarnings("unchecked") + private Stream> outboundPackets() { return ((EmbeddedChannel) connection.connection.channel()).outboundMessages().stream() - .filter(type::isInstance).map(type::cast); + .filter(Packet.class::isInstance).map(obj -> (Packet) obj) + .flatMap((Function, Stream>>) packet -> { + if (!(packet instanceof ClientboundBundlePacket clientboundBundlePacket)) return Stream.of(packet); + + return StreamSupport.stream(clientboundBundlePacket.subPackets().spliterator(), false) + .map(obj -> (Packet) obj); + }); + } + + public > Stream getOutboundPackets(Class type) { + return outboundPackets().filter(type::isInstance).map(type::cast); + } + + public Stream getOutboundPayloads(Class type) { + return getOutboundPackets(ClientboundCustomPayloadPacket.class) + .map(ClientboundCustomPayloadPacket::payload) + .filter(type::isInstance) + .map(type::cast); } } diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/TestFrameworkImpl.java b/testframework/src/main/java/net/neoforged/testframework/impl/TestFrameworkImpl.java index 627833602b..e6c396a273 100644 --- a/testframework/src/main/java/net/neoforged/testframework/impl/TestFrameworkImpl.java +++ b/testframework/src/main/java/net/neoforged/testframework/impl/TestFrameworkImpl.java @@ -60,7 +60,6 @@ import net.neoforged.neoforge.event.server.ServerStartedEvent; import net.neoforged.neoforge.event.server.ServerStoppedEvent; import net.neoforged.neoforge.network.PacketDistributor; -import net.neoforged.neoforge.network.simple.SimpleChannel; import net.neoforged.testframework.Test; import net.neoforged.testframework.TestListener; import net.neoforged.testframework.annotation.OnInit; @@ -69,9 +68,9 @@ import net.neoforged.testframework.conf.FrameworkConfiguration; import net.neoforged.testframework.gametest.DynamicStructureTemplates; import net.neoforged.testframework.group.Group; -import net.neoforged.testframework.impl.packet.ChangeEnabledPacket; -import net.neoforged.testframework.impl.packet.ChangeStatusPacket; -import net.neoforged.testframework.impl.packet.TFPackets; +import net.neoforged.testframework.impl.packet.ChangeEnabledPayload; +import net.neoforged.testframework.impl.packet.ChangeStatusPayload; +import net.neoforged.testframework.impl.packet.TestFrameworkPayloadInitialization; import org.apache.commons.lang3.StringUtils; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Nullable; @@ -111,8 +110,6 @@ public class TestFrameworkImpl implements MutableTestFramework { private final ResourceLocation id; private final TestsImpl tests = new TestsImpl(); - private final SimpleChannel channel; - private @Nullable MinecraftServer server; private final DynamicStructureTemplates structures; private final SummaryDumper summaryDumper; @@ -124,7 +121,6 @@ public TestFrameworkImpl(FrameworkConfiguration configuration) { this.configuration = configuration; this.id = configuration.id(); - this.channel = configuration.networkingChannel(); this.structures = new DynamicStructureTemplates(); this.summaryDumper = new SummaryDumper(this); @@ -262,7 +258,7 @@ public void init(final IEventBus modBus, final ModContainer container) { } }); - modBus.addListener(new TFPackets(channel, this)::onCommonSetup); + modBus.addListener(new TestFrameworkPayloadInitialization(this)::onNetworkSetup); modBus.addListener((final RegisterGameTestsEvent event) -> event.register(GameTestRegistration.REGISTER_METHOD)); if (FMLLoader.getDist().isClient()) { @@ -327,10 +323,10 @@ public void changeStatus(Test test, Test.Status newStatus, @Nullable Entity chan logger.info("Status of test '{}' has had status changed to {}{}.", test.id(), newStatus, changer instanceof Player player ? " by " + player.getGameProfile().getName() : ""); - final ChangeStatusPacket packet = new ChangeStatusPacket(this, test.id(), newStatus); + final ChangeStatusPayload packet = new ChangeStatusPayload(this, test.id(), newStatus); sendPacketIfOn( - () -> channel.send(PacketDistributor.ALL.noArg(), packet), - () -> channel.sendToServer(packet), + () -> PacketDistributor.ALL.noArg().send(packet), + () -> PacketDistributor.SERVER.noArg().send(packet), null); } @@ -354,10 +350,10 @@ public void setEnabled(Test test, boolean enabled, @Nullable Entity changer) { tests.globalListeners.forEach(listenerConsumer); test.listeners().forEach(listenerConsumer); - final ChangeEnabledPacket packet = new ChangeEnabledPacket(TestFrameworkImpl.this, test.id(), enabled); + final ChangeEnabledPayload packet = new ChangeEnabledPayload(TestFrameworkImpl.this, test.id(), enabled); sendPacketIfOn( - () -> channel.send(PacketDistributor.ALL.noArg(), packet), - () -> channel.sendToServer(packet), + () -> PacketDistributor.ALL.noArg().send(packet), + () -> PacketDistributor.SERVER.noArg().send(packet), null); } diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPacket.java b/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPayload.java similarity index 51% rename from testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPacket.java rename to testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPayload.java index 3675f9f2a0..36e90b4ed1 100644 --- a/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPacket.java +++ b/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeEnabledPayload.java @@ -8,28 +8,26 @@ import java.util.Objects; import java.util.function.Consumer; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.simple.SimpleMessage; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; import net.neoforged.testframework.conf.Feature; import net.neoforged.testframework.impl.MutableTestFramework; +import org.jetbrains.annotations.NotNull; -public record ChangeEnabledPacket(MutableTestFramework framework, String testId, boolean enabled) implements SimpleMessage { - @Override - public void encode(FriendlyByteBuf buf) { - buf.writeUtf(testId); - buf.writeBoolean(enabled); - } +public record ChangeEnabledPayload(MutableTestFramework framework, String testId, boolean enabled) implements CustomPacketPayload { - @Override - public void handleMainThread(NetworkEvent.Context context) { - switch (context.getDirection().getReceptionSide()) { + public static final ResourceLocation ID = new ResourceLocation("neoforge", "tf_change_enabled"); + + public void handle(PlayPayloadContext context) { + switch (context.flow().getReceptionSide()) { case CLIENT -> { final Consumer enablerer = enabled ? id -> framework.tests().enable(id) : id -> framework.tests().disable(id); enablerer.accept(testId); } case SERVER -> { - final ServerPlayer player = Objects.requireNonNull(context.getSender()); + final Player player = context.player().orElseThrow(); if (framework.configuration().isEnabled(Feature.CLIENT_MODIFICATIONS) && Objects.requireNonNull(player.getServer()).getPlayerList().isOp(player.getGameProfile())) { framework.tests().byId(testId).ifPresent(test -> framework.setEnabled(test, enabled, player)); } @@ -37,7 +35,18 @@ public void handleMainThread(NetworkEvent.Context context) { } } - public static ChangeEnabledPacket decode(MutableTestFramework framework, FriendlyByteBuf buf) { - return new ChangeEnabledPacket(framework, buf.readUtf(), buf.readBoolean()); + public static ChangeEnabledPayload decode(MutableTestFramework framework, FriendlyByteBuf buf) { + return new ChangeEnabledPayload(framework, buf.readUtf(), buf.readBoolean()); + } + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeUtf(testId); + buf.writeBoolean(enabled); + } + + @Override + public @NotNull ResourceLocation id() { + return ID; } } diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPacket.java b/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPacket.java deleted file mode 100644 index 1e8bd2314f..0000000000 --- a/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPacket.java +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.testframework.impl.packet; - -import java.util.Objects; -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.server.level.ServerPlayer; -import net.neoforged.neoforge.network.NetworkEvent; -import net.neoforged.neoforge.network.simple.SimpleMessage; -import net.neoforged.testframework.Test; -import net.neoforged.testframework.conf.Feature; -import net.neoforged.testframework.impl.MutableTestFramework; - -public record ChangeStatusPacket(MutableTestFramework framework, String testId, Test.Status status) implements SimpleMessage { - @Override - public void encode(FriendlyByteBuf buf) { - buf.writeUtf(testId); - buf.writeEnum(status.result()); - buf.writeUtf(status.message()); - } - - @Override - public void handleMainThread(NetworkEvent.Context context) { - switch (context.getDirection().getReceptionSide()) { - case CLIENT -> framework.tests().setStatus(testId, status); - case SERVER -> { - final ServerPlayer player = Objects.requireNonNull(context.getSender()); - if (framework.configuration().isEnabled(Feature.CLIENT_MODIFICATIONS) && Objects.requireNonNull(player.getServer()).getPlayerList().isOp(player.getGameProfile())) { - framework.tests().byId(testId).ifPresent(test -> framework.changeStatus(test, status, player)); - } - } - } - } - - public static ChangeStatusPacket decode(MutableTestFramework framework, FriendlyByteBuf buf) { - return new ChangeStatusPacket(framework, buf.readUtf(), new Test.Status(buf.readEnum(Test.Result.class), buf.readUtf())); - } -} diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPayload.java b/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPayload.java new file mode 100644 index 0000000000..2f4454f5e6 --- /dev/null +++ b/testframework/src/main/java/net/neoforged/testframework/impl/packet/ChangeStatusPayload.java @@ -0,0 +1,50 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.testframework.impl.packet; + +import java.util.Objects; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.entity.player.Player; +import net.neoforged.neoforge.network.handling.PlayPayloadContext; +import net.neoforged.testframework.Test; +import net.neoforged.testframework.conf.Feature; +import net.neoforged.testframework.impl.MutableTestFramework; +import org.jetbrains.annotations.NotNull; + +public record ChangeStatusPayload(MutableTestFramework framework, String testId, Test.Status status) implements CustomPacketPayload { + + public static final ResourceLocation ID = new ResourceLocation("neoforge", "tf_change_status"); + + @Override + public void write(FriendlyByteBuf buf) { + buf.writeUtf(testId); + buf.writeEnum(status.result()); + buf.writeUtf(status.message()); + } + + @Override + public @NotNull ResourceLocation id() { + return ID; + } + + public void handle(PlayPayloadContext context) { + switch (context.flow().getReceptionSide()) { + case CLIENT -> framework.tests().setStatus(testId, status); + case SERVER -> { + final Player player = context.player().orElseThrow(); + if (framework.configuration().isEnabled(Feature.CLIENT_MODIFICATIONS) && Objects.requireNonNull(player.getServer()).getPlayerList().isOp(player.getGameProfile())) { + framework.tests().byId(testId).ifPresent(test -> framework.changeStatus(test, status, player)); + } + } + } + } + + public static ChangeStatusPayload decode(MutableTestFramework framework, FriendlyByteBuf buf) { + return new ChangeStatusPayload(framework, buf.readUtf(), new Test.Status(buf.readEnum(Test.Result.class), buf.readUtf())); + } +} diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/packet/TFPackets.java b/testframework/src/main/java/net/neoforged/testframework/impl/packet/TFPackets.java deleted file mode 100644 index 51902dc416..0000000000 --- a/testframework/src/main/java/net/neoforged/testframework/impl/packet/TFPackets.java +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright (c) NeoForged and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.neoforged.testframework.impl.packet; - -import java.util.function.BiFunction; -import net.minecraft.network.FriendlyByteBuf; -import net.neoforged.bus.api.SubscribeEvent; -import net.neoforged.fml.event.lifecycle.FMLCommonSetupEvent; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import net.neoforged.neoforge.network.simple.SimpleMessage; -import net.neoforged.testframework.impl.MutableTestFramework; -import org.jetbrains.annotations.ApiStatus; - -@ApiStatus.Internal -public record TFPackets(SimpleChannel channel, MutableTestFramework framework) { - @SubscribeEvent - public void onCommonSetup(final FMLCommonSetupEvent event) { - class Registrar { - private final SimpleChannel channel; - int id = 0; - - Registrar(SimpleChannel channel) { - this.channel = channel; - } - -

void register(Class

pkt, BiFunction decoder) { - channel.simpleMessageBuilder(pkt, id++) - .decoder(buf -> decoder.apply(framework, buf)) - .add(); - } - } - - final Registrar registrar = new Registrar(channel); - registrar.register(ChangeStatusPacket.class, ChangeStatusPacket::decode); - registrar.register(ChangeEnabledPacket.class, ChangeEnabledPacket::decode); - } -} diff --git a/testframework/src/main/java/net/neoforged/testframework/impl/packet/TestFrameworkPayloadInitialization.java b/testframework/src/main/java/net/neoforged/testframework/impl/packet/TestFrameworkPayloadInitialization.java new file mode 100644 index 0000000000..079ee1b12e --- /dev/null +++ b/testframework/src/main/java/net/neoforged/testframework/impl/packet/TestFrameworkPayloadInitialization.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) NeoForged and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.neoforged.testframework.impl.packet; + +import net.neoforged.bus.api.SubscribeEvent; +import net.neoforged.neoforge.internal.versions.neoforge.NeoForgeVersion; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.registration.IPayloadRegistrar; +import net.neoforged.testframework.impl.MutableTestFramework; +import org.jetbrains.annotations.ApiStatus; + +@ApiStatus.Internal +public record TestFrameworkPayloadInitialization(MutableTestFramework framework) { + + @SubscribeEvent + public void onNetworkSetup(final RegisterPayloadHandlerEvent event) { + + final IPayloadRegistrar registrar = event.registrar(NeoForgeVersion.MOD_ID); + + registrar.play(ChangeStatusPayload.ID, buf -> ChangeStatusPayload.decode(framework, buf), (payload, context) -> context.workHandler().submitAsync(() -> payload.handle(context))); + registrar.play(ChangeEnabledPayload.ID, buf -> ChangeEnabledPayload.decode(framework, buf), (payload, context) -> context.workHandler().submitAsync(() -> payload.handle(context))); + } +} diff --git a/tests/src/main/java/net/neoforged/neoforge/debug/entity/EntityTests.java b/tests/src/main/java/net/neoforged/neoforge/debug/entity/EntityTests.java index 8437e4a369..0ec180dc8f 100644 --- a/tests/src/main/java/net/neoforged/neoforge/debug/entity/EntityTests.java +++ b/tests/src/main/java/net/neoforged/neoforge/debug/entity/EntityTests.java @@ -5,9 +5,178 @@ package net.neoforged.neoforge.debug.entity; +import java.util.function.Consumer; +import net.minecraft.client.renderer.entity.NoopRenderer; +import net.minecraft.core.BlockPos; +import net.minecraft.gametest.framework.GameTest; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.entity.Entity; +import net.minecraft.world.entity.EntityType; +import net.minecraft.world.entity.MobCategory; +import net.minecraft.world.level.GameType; +import net.minecraft.world.level.Level; +import net.neoforged.neoforge.entity.IEntityWithComplexSpawn; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.payload.AdvancedAddEntityPayload; +import net.neoforged.testframework.DynamicTest; import net.neoforged.testframework.annotation.ForEachTest; +import net.neoforged.testframework.annotation.TestHolder; +import net.neoforged.testframework.gametest.EmptyTemplate; +import net.neoforged.testframework.registration.RegistrationHelper; +import org.jetbrains.annotations.NotNull; @ForEachTest(groups = EntityTests.GROUP) public class EntityTests { public static final String GROUP = "level.entity"; + + @GameTest + @EmptyTemplate + @TestHolder(description = "Tests if custom fence gates without wood types work, allowing for the use of the vanilla block for non-wooden gates") + static void customSpawnLogic(final DynamicTest test, final RegistrationHelper reg) { + final var usingForgeAdvancedSpawn = reg.entityTypes().registerType("complex_spawn", () -> EntityType.Builder.of(CustomComplexSpawnEntity::new, MobCategory.AMBIENT) + .sized(1, 1)).withLang("Custom complex spawn egg").withRenderer(() -> NoopRenderer::new); + final var usingCustomPayloadsSpawn = reg.entityTypes().registerType("adapted_spawn", () -> EntityType.Builder.of(AdaptedSpawnEntity::new, MobCategory.AMBIENT) + .sized(1, 1)).withLang("Adapted complex spawn egg").withRenderer(() -> NoopRenderer::new); + final var simpleSpawn = reg.entityTypes().registerType("simple_spawn", () -> EntityType.Builder.of(SimpleEntity::new, MobCategory.AMBIENT) + .sized(1, 1)).withLang("Simple spawn egg").withRenderer(() -> NoopRenderer::new); + + reg.eventListeners().accept((Consumer) event -> event.registrar("test") + .play(CustomSyncPayload.ID, CustomSyncPayload::new, (payload, context) -> {})); + + test.onGameTest(helper -> { + helper.startSequence(() -> helper.makeTickingMockServerPlayerInCorner(GameType.SURVIVAL)) + .thenExecute(() -> helper.spawn(usingForgeAdvancedSpawn.get(), new BlockPos(1, 1, 1))) + + // Check if forge payload was sent + .thenExecute(player -> helper.assertTrue( + player.getOutboundPayloads(AdvancedAddEntityPayload.class) + .findAny().isPresent(), + "Advanced payload for custom spawn was not send")) + .thenSucceed(); + helper.startSequence(() -> helper.makeTickingMockServerPlayerInCorner(GameType.SURVIVAL)) + .thenExecute(() -> helper.spawn(usingCustomPayloadsSpawn.get(), new BlockPos(1, 1, 1))) + + // Check if custom payload was sent + .thenExecute(player -> helper.assertTrue( + player.getOutboundPayloads(CustomSyncPayload.class) + .findAny().isPresent(), + "Custom sync payload for adapted spawn was not send")) + .thenSucceed(); + helper.startSequence(() -> helper.makeTickingMockServerPlayerInCorner(GameType.SURVIVAL)) + .thenExecute(() -> helper.spawn(simpleSpawn.get(), new BlockPos(1, 1, 1))) + + // Check if custom payload was sent + .thenExecute(player -> helper.assertTrue( + player.getOutboundPayloads(AdvancedAddEntityPayload.class) + .findAny().isEmpty(), + "Advanced payload for custom spawn was send")) + .thenExecute(player -> helper.assertTrue( + player.getOutboundPayloads(CustomSyncPayload.class) + .findAny().isEmpty(), + "Custom sync payload for custom spawn was send")) + .thenSucceed(); + }); + } + + public static final class CustomComplexSpawnEntity extends Entity implements IEntityWithComplexSpawn { + + public CustomComplexSpawnEntity(EntityType type, Level level) { + super(type, level); + } + + @Override + protected void defineSynchedData() { + + } + + @Override + protected void readAdditionalSaveData(@NotNull CompoundTag tag) { + + } + + @Override + protected void addAdditionalSaveData(@NotNull CompoundTag tag) { + + } + + @Override + public void writeSpawnData(FriendlyByteBuf buffer) { + + } + + @Override + public void readSpawnData(FriendlyByteBuf additionalData) { + + } + } + + public static final class AdaptedSpawnEntity extends Entity { + + public AdaptedSpawnEntity(EntityType type, Level level) { + super(type, level); + } + + @Override + protected void defineSynchedData() { + + } + + @Override + protected void readAdditionalSaveData(@NotNull CompoundTag tag) { + + } + + @Override + protected void addAdditionalSaveData(@NotNull CompoundTag tag) { + + } + + @Override + public void sendPairingData(ServerPlayer serverPlayer, Consumer bundleBuilder) { + bundleBuilder.accept(new CustomSyncPayload()); + } + } + + public static final class SimpleEntity extends Entity { + + public SimpleEntity(EntityType type, Level level) { + super(type, level); + } + + @Override + protected void defineSynchedData() { + + } + + @Override + protected void readAdditionalSaveData(@NotNull CompoundTag tag) { + + } + + @Override + protected void addAdditionalSaveData(@NotNull CompoundTag tag) { + + } + } + + public record CustomSyncPayload() implements CustomPacketPayload { + + private static final ResourceLocation ID = new ResourceLocation("test", "custom_sync_payload"); + + public CustomSyncPayload(FriendlyByteBuf buf) { + this(); + } + + @Override + public void write(@NotNull FriendlyByteBuf buf) {} + + @Override + public @NotNull ResourceLocation id() { + return ID; + } + } } diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ContainerTypeTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ContainerTypeTest.java index d2ac2c996d..9187570474 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ContainerTypeTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ContainerTypeTest.java @@ -12,7 +12,6 @@ import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.InteractionHand; import net.minecraft.world.MenuProvider; import net.minecraft.world.SimpleContainer; @@ -30,7 +29,6 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; -import net.neoforged.neoforge.network.NetworkHooks; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.RegisterEvent; @@ -82,7 +80,7 @@ public ContainerTypeTest(IEventBus modEventBus) { } private void registerContainers(final RegisterEvent event) { - event.register(Registries.MENU, helper -> helper.register("container", IMenuTypeExtension.create(TestContainer::new))); + event.register(Registries.MENU, helper -> helper.register(new ResourceLocation("containertypetest", "container"), IMenuTypeExtension.create(TestContainer::new))); } private void setup(FMLClientSetupEvent event) { @@ -93,7 +91,7 @@ private void onRightClick(PlayerInteractEvent.RightClickBlock event) { if (!event.getLevel().isClientSide && event.getHand() == InteractionHand.MAIN_HAND) { if (event.getLevel().getBlockState(event.getPos()).getBlock() == Blocks.SPONGE) { String text = "Hello World!"; - NetworkHooks.openScreen((ServerPlayer) event.getEntity(), new MenuProvider() { + event.getEntity().openMenu(new MenuProvider() { @Override public AbstractContainerMenu createMenu(int p_createMenu_1_, Inventory p_createMenu_2_, Player p_createMenu_3_) { SimpleContainer inv = new SimpleContainer(9); diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ModMismatchTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ModMismatchTest.java index 3fe2b74c53..cf2bec30e4 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ModMismatchTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/misc/ModMismatchTest.java @@ -5,27 +5,32 @@ package net.neoforged.neoforge.oldtest.misc; -import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.minecraft.sounds.SoundEvent; +import net.neoforged.api.distmarker.Dist; import net.neoforged.bus.api.IEventBus; import net.neoforged.fml.common.Mod; import net.neoforged.fml.loading.FMLEnvironment; import net.neoforged.neoforge.client.gui.ModMismatchDisconnectedScreen; -import net.neoforged.neoforge.network.NetworkRegistry; -import net.neoforged.neoforge.network.simple.SimpleChannel; -import net.neoforged.neoforge.registries.DeferredRegister; +import net.neoforged.neoforge.network.event.RegisterPayloadHandlerEvent; +import net.neoforged.neoforge.network.handling.ConfigurationPayloadContext; +import net.neoforged.neoforge.network.handling.IConfigurationPayloadHandler; /** - * This test mod provides a way to register a {@link SimpleChannel} with a different protocol version on the client and the server to cause a mod channel mismatch. - * Additionally, this test mod can register a registry object for the dedicated server only, causing a registry mismatch. Registering a registry object for the client only is not supported, since that would not cause a registry mismatch. + * This test mod provides a way to register a {@link CustomPacketPayload} with a different protocol version on the client and the server to cause a mod channel mismatch. * With this test mod and at least one of its features enabled, a {@link ModMismatchDisconnectedScreen} should appear when trying to join a test server, * displaying detailed information about why the handshake failed. * In case of a mismatch, the two displayed mod versions will be the same due to not being able to specify a different client and server mod version of this test mod. * This test mod is disabled by default to ensure that users can join test servers without needing to specifically disable this test mod. + *

+ * In the past this test mod also registered a {@link SoundEvent} to cause a registry mismatch, but this is no longer the case, + * as the network negotiation does not care for registry mismatches anymore. + *

*/ @Mod(ModMismatchTest.MOD_ID) -public class ModMismatchTest { +public class ModMismatchTest implements IConfigurationPayloadHandler { public static final String MOD_ID = "mod_mismatch_test"; private static final boolean ENABLED = false; @@ -35,21 +40,48 @@ public class ModMismatchTest { // Additionally, if the channel is missing for the client, a S2CModMismatchData packet will be sent to the client, containing all the information about the channel mismatch detected on the server. private static final boolean REGISTER_FOR_SERVER = true; private static final boolean REGISTER_FOR_CLIENT = true; - // Enabling this field (and disabling the two above to not cause a channel mismatch) will cause a registry mismatch due to a server registry entry not being present on the client. Since this test mod is loaded on both dists, a mod mismatch will be displayed as the cause. - private static final boolean REGISTER_REGISTRY_ENTRY = false; - private static final DeferredRegister SOUND_EVENTS = DeferredRegister.create(BuiltInRegistries.SOUND_EVENT, MOD_ID); - private static final String CHANNEL_PROTOCOL_VERSION = FMLEnvironment.dist.isClient() ? "V1" : "V2"; + private static final String CHANNEL_PROTOCOL_VERSION = FMLEnvironment.dist == Dist.CLIENT ? "V1" : "V2"; - public ModMismatchTest(IEventBus eventBus) { + public ModMismatchTest(IEventBus modBus) { if (ENABLED) { - if ((FMLEnvironment.dist.isDedicatedServer() && REGISTER_FOR_SERVER) || (FMLEnvironment.dist.isClient() && REGISTER_FOR_CLIENT)) { - NetworkRegistry.newSimpleChannel(new ResourceLocation(MOD_ID, "channel"), () -> CHANNEL_PROTOCOL_VERSION, p -> p.equals(CHANNEL_PROTOCOL_VERSION), (p) -> p.equals(CHANNEL_PROTOCOL_VERSION)); - } - if (REGISTER_REGISTRY_ENTRY && FMLEnvironment.dist.isDedicatedServer()) { - SOUND_EVENTS.register("mismatching_sound_event", () -> SoundEvent.createVariableRangeEvent(new ResourceLocation(MOD_ID, "server.connect.fail"))); - SOUND_EVENTS.register(eventBus); - } + modBus.addListener(RegisterPayloadHandlerEvent.class, this::onRegisterPacketHandler); } } + + private void onRegisterPacketHandler(RegisterPayloadHandlerEvent event) { + if ((FMLEnvironment.dist == Dist.DEDICATED_SERVER && REGISTER_FOR_SERVER) || (FMLEnvironment.dist == Dist.CLIENT && REGISTER_FOR_CLIENT)) { + event + .registrar(MOD_ID) + .versioned(CHANNEL_PROTOCOL_VERSION) + .configuration( + ModMismatchPayload.ID, + ModMismatchPayload::new, + this); + } + } + + @Override + public void handle(ModMismatchPayload payload, ConfigurationPayloadContext context) { + //Noop + } + + public record ModMismatchPayload() implements CustomPacketPayload { + + private static final ResourceLocation ID = new ResourceLocation(MOD_ID, "mod_mismatch"); + + public ModMismatchPayload(FriendlyByteBuf buf) { + this(); + } + + @Override + public void write(FriendlyByteBuf p_294947_) {} + + @Override + public ResourceLocation id() { + return ID; + } + + } + } diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookExtensionTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookExtensionTest.java index 8b268db1b4..9a65dfa711 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookExtensionTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/recipebook/RecipeBookExtensionTest.java @@ -9,7 +9,6 @@ import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.network.chat.Component; import net.minecraft.resources.ResourceLocation; -import net.minecraft.server.level.ServerPlayer; import net.minecraft.world.SimpleContainer; import net.minecraft.world.SimpleMenuProvider; import net.minecraft.world.inventory.ContainerLevelAccess; @@ -27,7 +26,6 @@ import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.common.extensions.IMenuTypeExtension; import net.neoforged.neoforge.event.entity.player.PlayerInteractEvent; -import net.neoforged.neoforge.network.NetworkHooks; import net.neoforged.neoforge.registries.DeferredHolder; import net.neoforged.neoforge.registries.DeferredRegister; @@ -61,8 +59,9 @@ public RecipeBookExtensionTest(IEventBus modBus) { private void onRightClick(PlayerInteractEvent.RightClickBlock event) { if (event.getLevel().isClientSide) return; - if (event.getLevel().getBlockState(event.getPos()).getBlock() == Blocks.GRASS_BLOCK) - NetworkHooks.openScreen((ServerPlayer) event.getEntity(), new SimpleMenuProvider((id, inv, p) -> new RecipeBookTestMenu(id, inv, ContainerLevelAccess.create(event.getLevel(), event.getPos())), Component.literal("Test"))); + if (event.getLevel().getBlockState(event.getPos()).getBlock() == Blocks.GRASS_BLOCK) { + event.getEntity().openMenu(new SimpleMenuProvider((id, inv, p) -> new RecipeBookTestMenu(id, inv, ContainerLevelAccess.create(event.getLevel(), event.getPos())), Component.literal("Test"))); + } } public static ResourceLocation getId(String name) { diff --git a/tests/src/main/java/net/neoforged/neoforge/oldtest/world/LoginPacketSplitTest.java b/tests/src/main/java/net/neoforged/neoforge/oldtest/world/LoginPacketSplitTest.java index 18be1d09dd..3adc8ac4aa 100644 --- a/tests/src/main/java/net/neoforged/neoforge/oldtest/world/LoginPacketSplitTest.java +++ b/tests/src/main/java/net/neoforged/neoforge/oldtest/world/LoginPacketSplitTest.java @@ -6,14 +6,18 @@ package net.neoforged.neoforge.oldtest.world; import com.google.common.base.Stopwatch; +import com.google.gson.Gson; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.mojang.brigadier.Command; import com.mojang.logging.LogUtils; import com.mojang.serialization.Codec; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.JsonOps; import com.mojang.serialization.Lifecycle; import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.buffer.Unpooled; +import io.netty.handler.codec.EncoderException; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; @@ -26,6 +30,7 @@ import java.util.function.Supplier; import java.util.stream.Collectors; import net.minecraft.SharedConstants; +import net.minecraft.Util; import net.minecraft.commands.Commands; import net.minecraft.core.MappedRegistry; import net.minecraft.core.Registry; @@ -50,7 +55,6 @@ import net.neoforged.neoforge.client.event.RegisterClientCommandsEvent; import net.neoforged.neoforge.common.NeoForge; import net.neoforged.neoforge.event.AddPackFindersEvent; -import net.neoforged.neoforge.network.filters.NeoForgeConnectionNetworkFilter; import net.neoforged.neoforge.registries.DataPackRegistryEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -63,7 +67,7 @@ * registryaccess in the packet would be, and how much {@code %} of the packet limit is represents.
* Connect to the server from the client, and if you successfully connect and the {@code /big_data} command * reports 50000 entries then the packet has been successfully split.

- * To test if the packet is too large simply remove the login packet from the {@link NeoForgeConnectionNetworkFilter} + * To test if the packet is too large simply remove the login packet from the {@link net.neoforged.neoforge.network.filters.GenericPacketSplitter} * and try connecting again. You should see the connection fail. */ @@ -71,7 +75,8 @@ public class LoginPacketSplitTest { public static final Logger LOG = LogUtils.getLogger(); public static final String MOD_ID = "login_packet_split_test"; - public static final boolean ENABLED = false; + public static final boolean ENABLED = true; + private static final Gson GSON = new Gson(); public static final ResourceKey> BIG_DATA = ResourceKey.createRegistryKey(new ResourceLocation(MOD_ID, "big_data")); public LoginPacketSplitTest(IEventBus bus) { @@ -121,7 +126,7 @@ private void generateEntries(InMemoryResourcePack pack) { final FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer()); record RegistryData(Registry registry) {} - buf.writeJsonWithCodec(RecordCodecBuilder.create(in -> in.group( + writeJsonWithCodec(buf, RecordCodecBuilder.create(in -> in.group( RegistryCodecs.networkCodec(BIG_DATA, Lifecycle.stable(), BigData.CODEC).fieldOf("registry").forGetter(RegistryData::registry)).apply(in, RegistryData::new)), new RegistryData(dummyRegistry)); // RegistryCodecs.networkCodec returns a list codec, and writeWithNbt doesn't like non-compounds final int size = buf.writerIndex(); @@ -250,4 +255,11 @@ public static byte[] fromJson(JsonElement json) { return GsonHelper.toStableString(json).getBytes(StandardCharsets.UTF_8); } } + + public void writeJsonWithCodec(FriendlyByteBuf buf, Codec codec, T instance) { + DataResult dataresult = codec.encodeStart(JsonOps.INSTANCE, instance); + final String s = GSON.toJson(Util.getOrThrow(dataresult, p_261421_ -> new EncoderException("Failed to encode: " + p_261421_ + " " + instance))); + buf.writeVarInt(s.length()); + buf.writeUtf(s, s.length()); + } }