diff --git a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy index 0a5920e4ab..89192dff55 100644 --- a/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy +++ b/buildSrc/src/main/groovy/net/minecraftforge/forge/tasks/checks/CheckPatches.groovy @@ -140,7 +140,7 @@ abstract class CheckPatches extends CheckTask { final nextLineChange = i + 1 < lines.size() && lines[i + 1].startsWithAny('+','-') if (!prevLineChange && !nextLineChange) { - reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath", false) + reporter.report("Patch contains white space change in valid hunk at line ${i + 1}, file: $patchPath \n$prevLine\n$line\n${lines[i+1]}", false) } } diff --git a/patches/minecraft/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch b/patches/minecraft/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch index 3efd8334cb..c28c1f592f 100644 --- a/patches/minecraft/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch +++ b/patches/minecraft/net/minecraft/client/multiplayer/ClientCommonPacketListenerImpl.java.patch @@ -4,7 +4,7 @@ @Override public void handleCustomPayload(ClientboundCustomPayloadPacket p_298103_) { -+ if (net.minecraftforge.common.ForgeHooks.onCustomPayload(p_298103_, this.connection)) return; ++ if (net.minecraftforge.common.ForgeHooks.onCustomPayload(p_298103_.payload(), this.connection)) return; CustomPacketPayload custompacketpayload = p_298103_.payload(); if (!(custompacketpayload instanceof DiscardedPayload)) { PacketUtils.ensureRunningOnSameThread(p_298103_, this, this.minecraft); diff --git a/patches/minecraft/net/minecraft/network/Connection.java.patch b/patches/minecraft/net/minecraft/network/Connection.java.patch index 6d32f09275..f436089a36 100644 --- a/patches/minecraft/net/minecraft/network/Connection.java.patch +++ b/patches/minecraft/net/minecraft/network/Connection.java.patch @@ -1,15 +1,20 @@ --- a/net/minecraft/network/Connection.java +++ b/net/minecraft/network/Connection.java -@@ -107,9 +_,12 @@ +@@ -107,9 +_,17 @@ private volatile Component delayedDisconnect; @Nullable BandwidthDebugMonitor bandwidthDebugMonitor; + private java.util.function.Consumer activationHandler; -+ private final net.minecraftforge.common.util.PacketLogger packetLogger; ++ private final net.minecraftforge.common.util.PacketLogger packetLogger = new net.minecraftforge.common.util.PacketLogger(this); ++ private ProtocolInfo outboundProtocol = null; ++ private ProtocolInfo inboundProtocol = null; public Connection(PacketFlow p_129482_) { this.receiving = p_129482_; -+ this.packetLogger = new net.minecraftforge.common.util.PacketLogger(this); ++ if (this.receiving == PacketFlow.SERVERBOUND) ++ this.inboundProtocol = INITIAL_PROTOCOL; ++ else ++ this.outboundProtocol = INITIAL_PROTOCOL; } @Override @@ -29,14 +34,23 @@ genericsFtw(p_129488_, packetlistener); } catch (RunningOnDifferentThreadException runningondifferentthreadexception) { } catch (RejectedExecutionException rejectedexecutionexception) { -@@ -249,6 +_,7 @@ - p_326044_ -> p_326044_.pipeline().addAfter("encoder", "unbundler", packetbundleunpacker) - ); - } -+ unconfiguredpipelinehandler$outboundconfigurationtask = unconfiguredpipelinehandler$outboundconfigurationtask.andThen(f -> this.protocol = p_329145_.id()); - - boolean flag = p_329145_.id() == ConnectionProtocol.LOGIN; - syncAfterConfigurationChange(this.channel.writeAndFlush(unconfiguredpipelinehandler$outboundconfigurationtask.andThen(p_326048_ -> this.sendLoginDisconnect = flag))); +@@ -218,6 +_,7 @@ + if (p_333271_.flow() != this.getReceiving()) { + throw new IllegalStateException("Invalid inbound protocol: " + p_333271_.id()); + } else { ++ this.inboundProtocol = p_333271_; + this.packetListener = p_330962_; + this.disconnectListener = null; + UnconfiguredPipelineHandler.InboundConfigurationTask unconfiguredpipelinehandler$inboundconfigurationtask = UnconfiguredPipelineHandler.setupInboundProtocol( +@@ -241,6 +_,8 @@ + } else { + UnconfiguredPipelineHandler.OutboundConfigurationTask unconfiguredpipelinehandler$outboundconfigurationtask = UnconfiguredPipelineHandler.setupOutboundProtocol( + p_329145_ ++ ).andThen( ++ f -> this.outboundProtocol = p_329145_ + ); + BundlerInfo bundlerinfo = p_329145_.bundlerInfo(); + if (bundlerinfo != null) { @@ -290,7 +_,8 @@ this.disconnectListener = p_330656_; this.runOnceConnected(p_326042_ -> { @@ -81,7 +95,7 @@ new Bootstrap().group(LOCAL_WORKER_GROUP.get()).handler(new ChannelInitializer() { @Override protected void initChannel(Channel p_332618_) { -@@ -616,6 +_,15 @@ +@@ -616,6 +_,22 @@ public float getAverageSentPackets() { return this.averageSentPackets; @@ -91,9 +105,16 @@ + return this.channel; + } + -+ private ConnectionProtocol protocol = INITIAL_PROTOCOL.id(); + public ConnectionProtocol getProtocol() { -+ return protocol; ++ return outboundProtocol != null ? outboundProtocol.id() : inboundProtocol.id(); ++ } ++ ++ public ProtocolInfo getInboundProtocolInfo() { ++ return this.inboundProtocol; ++ } ++ ++ public ProtocolInfo getOutputboundProtocolInfo() { ++ return this.outboundProtocol; } public void setBandwidthLogger(LocalSampleLogger p_333554_) { diff --git a/patches/minecraft/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch b/patches/minecraft/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch index b988bc5fb7..ade480adf7 100644 --- a/patches/minecraft/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch +++ b/patches/minecraft/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java.patch @@ -1,20 +1,20 @@ --- a/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java +++ b/net/minecraft/network/protocol/common/ClientboundCustomPayloadPacket.java -@@ -32,7 +_,7 @@ - import net.minecraft.network.protocol.common.custom.WorldGenAttemptDebugPayload; - import net.minecraft.resources.ResourceLocation; - --public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet { -+public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet, net.minecraftforge.network.ICustomPacket { +@@ -35,7 +_,7 @@ + public record ClientboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet { private static final int MAX_PAYLOAD_SIZE = 1048576; public static final StreamCodec GAMEPLAY_STREAM_CODEC = CustomPacketPayload.codec( - p_330149_ -> DiscardedPayload.codec(p_330149_, 1048576), -@@ -77,4 +_,8 @@ - public void handle(ClientCommonPacketListener p_299773_) { - p_299773_.handleCustomPayload(this); - } -+ -+ @Override public @org.jetbrains.annotations.Nullable FriendlyByteBuf getInternalData() { return payload instanceof DiscardedPayload dc ? dc.data() : null; } -+ @Override public ResourceLocation getName() { return payload.type().id(); } -+ @Override public int getIndex() { return 0; } - } +- p_330149_ -> DiscardedPayload.codec(p_330149_, 1048576), ++ p_330149_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_330149_, 1048576), + Util.make( + Lists.newArrayList( + new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC), +@@ -64,7 +_,7 @@ + ) + .map(ClientboundCustomPayloadPacket::new, ClientboundCustomPayloadPacket::payload); + public static final StreamCodec CONFIG_STREAM_CODEC = CustomPacketPayload.codec( +- p_331803_ -> DiscardedPayload.codec(p_331803_, 1048576), ++ p_331803_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_331803_, 1048576), + List.of(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)) + ) + .map(ClientboundCustomPayloadPacket::new, ClientboundCustomPayloadPacket::payload); diff --git a/patches/minecraft/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch b/patches/minecraft/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch index 2be1567c83..201ed60972 100644 --- a/patches/minecraft/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch +++ b/patches/minecraft/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java.patch @@ -1,20 +1,11 @@ --- a/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java +++ b/net/minecraft/network/protocol/common/ServerboundCustomPayloadPacket.java -@@ -12,7 +_,7 @@ - import net.minecraft.network.protocol.common.custom.DiscardedPayload; - import net.minecraft.resources.ResourceLocation; - --public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet { -+public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet, net.minecraftforge.network.ICustomPacket { +@@ -15,7 +_,7 @@ + public record ServerboundCustomPayloadPacket(CustomPacketPayload payload) implements Packet { private static final int MAX_PAYLOAD_SIZE = 32767; public static final StreamCodec STREAM_CODEC = CustomPacketPayload.codec( - p_335203_ -> DiscardedPayload.codec(p_335203_, 32767), -@@ -29,4 +_,8 @@ - public void handle(ServerCommonPacketListener p_297991_) { - p_297991_.handleCustomPayload(this); - } -+ -+ @Override public @org.jetbrains.annotations.Nullable FriendlyByteBuf getInternalData() { return payload instanceof DiscardedPayload dc ? dc.data() : null; } -+ @Override public ResourceLocation getName() { return payload.type().id(); } -+ @Override public int getIndex() { return 0; } - } +- p_335203_ -> DiscardedPayload.codec(p_335203_, 32767), ++ p_335203_ -> net.minecraftforge.common.ForgeHooks.getCustomPayloadCodec(p_335203_, 32767), + Util.make(Lists.newArrayList(new CustomPacketPayload.TypeAndCodec<>(BrandPayload.TYPE, BrandPayload.STREAM_CODEC)), p_334419_ -> { + }) + ) diff --git a/patches/minecraft/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch b/patches/minecraft/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch deleted file mode 100644 index 89299209d8..0000000000 --- a/patches/minecraft/net/minecraft/network/protocol/common/custom/DiscardedPayload.java.patch +++ /dev/null @@ -1,32 +0,0 @@ ---- a/net/minecraft/network/protocol/common/custom/DiscardedPayload.java -+++ b/net/minecraft/network/protocol/common/custom/DiscardedPayload.java -@@ -4,18 +_,26 @@ - import net.minecraft.network.codec.StreamCodec; - import net.minecraft.resources.ResourceLocation; - --public record DiscardedPayload(ResourceLocation id) implements CustomPacketPayload { -+public record DiscardedPayload(ResourceLocation id, -+ @org.jetbrains.annotations.Nullable FriendlyByteBuf data, -+ java.util.function.Consumer encoder -+) implements CustomPacketPayload { - public static StreamCodec codec(ResourceLocation p_328904_, int p_334650_) { - return CustomPacketPayload.codec((p_330619_, p_329210_) -> { -+ p_330619_.encoder().accept(p_329210_); - }, p_333509_ -> { - int i = p_333509_.readableBytes(); - if (i >= 0 && i <= p_334650_) { -- p_333509_.skipBytes(i); -- return new DiscardedPayload(p_328904_); -+ var data = p_333509_.wrap(p_333509_.readBytes(i)); -+ return new DiscardedPayload(p_328904_, data, buf -> buf.writeBytes(data.slice())); - } else { - throw new IllegalArgumentException("Payload may not be larger than " + p_334650_ + " bytes"); - } - }); -+ } -+ -+ public DiscardedPayload(ResourceLocation id) { -+ this(id, null, buf -> {}); - } - - @Override diff --git a/patches/minecraft/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch b/patches/minecraft/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch deleted file mode 100644 index d3d2d62cd7..0000000000 --- a/patches/minecraft/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java.patch +++ /dev/null @@ -1,31 +0,0 @@ ---- a/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java -+++ b/net/minecraft/network/protocol/login/ClientboundCustomQueryPacket.java -@@ -8,7 +_,7 @@ - import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload; - import net.minecraft.resources.ResourceLocation; - --public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload payload) implements Packet { -+public record ClientboundCustomQueryPacket(int transactionId, CustomQueryPayload payload) implements Packet, net.minecraftforge.network.ICustomPacket { - public static final StreamCodec STREAM_CODEC = Packet.codec( - ClientboundCustomQueryPacket::write, ClientboundCustomQueryPacket::new - ); -@@ -25,8 +_,8 @@ - private static DiscardedQueryPayload readUnknownPayload(ResourceLocation p_299981_, FriendlyByteBuf p_297706_) { - int i = p_297706_.readableBytes(); - if (i >= 0 && i <= 1048576) { -- p_297706_.skipBytes(i); -- return new DiscardedQueryPayload(p_299981_); -+ var data = p_297706_.wrap(p_297706_.readBytes(i)); -+ return new DiscardedQueryPayload(p_299981_, data, buf -> buf.writeBytes(data.slice())); - } else { - throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); - } -@@ -46,4 +_,8 @@ - public void handle(ClientLoginPacketListener p_134754_) { - p_134754_.handleCustomQuery(this); - } -+ -+ @Override public int getIndex() { return transactionId(); } -+ @Override public ResourceLocation getName() { return this.payload.id(); } -+ @org.jetbrains.annotations.Nullable @Override public FriendlyByteBuf getInternalData() { return ((DiscardedQueryPayload)this.payload).data(); } - } diff --git a/patches/minecraft/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch b/patches/minecraft/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch deleted file mode 100644 index 011221cccb..0000000000 --- a/patches/minecraft/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java.patch +++ /dev/null @@ -1,31 +0,0 @@ ---- a/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java -+++ b/net/minecraft/network/protocol/login/ServerboundCustomQueryAnswerPacket.java -@@ -8,7 +_,7 @@ - import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload; - import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload; - --public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable CustomQueryAnswerPayload payload) implements Packet { -+public record ServerboundCustomQueryAnswerPacket(int transactionId, @Nullable CustomQueryAnswerPayload payload) implements Packet, net.minecraftforge.network.ICustomPacket { - public static final StreamCodec STREAM_CODEC = Packet.codec( - ServerboundCustomQueryAnswerPacket::write, ServerboundCustomQueryAnswerPacket::read - ); -@@ -26,8 +_,8 @@ - private static CustomQueryAnswerPayload readUnknownPayload(FriendlyByteBuf p_299934_) { - int i = p_299934_.readableBytes(); - if (i >= 0 && i <= 1048576) { -- p_299934_.skipBytes(i); -- return DiscardedQueryAnswerPayload.INSTANCE; -+ var data = p_299934_.wrap(p_299934_.readBytes(i)); -+ return new DiscardedQueryAnswerPayload(data, buf -> buf.writeBytes(data.slice())); - } else { - throw new IllegalArgumentException("Payload may not be larger than 1048576 bytes"); - } -@@ -46,4 +_,8 @@ - public void handle(ServerLoginPacketListener p_298492_) { - p_298492_.handleCustomQueryPacket(this); - } -+ -+ @Nullable @Override public FriendlyByteBuf getInternalData() { return this.payload instanceof DiscardedQueryAnswerPayload dc ? dc.data() : null; } -+ @Override public net.minecraft.resources.ResourceLocation getName() { return net.minecraftforge.network.NetworkInitialization.LOGIN_NAME; } -+ @Override public int getIndex() { return transactionId(); } - } diff --git a/patches/minecraft/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/patches/minecraft/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index 05df7ed702..d61dc1d1f9 100644 --- a/patches/minecraft/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/patches/minecraft/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -4,7 +4,7 @@ @Override public void handleCustomPayload(ServerboundCustomPayloadPacket p_300164_) { -+ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_300164_, this.connection); ++ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_300164_.payload(), this.connection); } @Override diff --git a/patches/minecraft/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/patches/minecraft/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 4b1936a9b8..0a8cef6694 100644 --- a/patches/minecraft/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/patches/minecraft/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -41,7 +41,7 @@ @Override public void handleCustomPayload(ServerboundCustomPayloadPacket p_329963_) { -+ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_329963_, this.connection); ++ net.minecraftforge.common.ForgeHooks.onCustomPayload(p_329963_.payload(), this.connection); } @Override diff --git a/src/main/java/net/minecraftforge/common/ForgeHooks.java b/src/main/java/net/minecraftforge/common/ForgeHooks.java index 73030bd641..f849cc11f2 100644 --- a/src/main/java/net/minecraftforge/common/ForgeHooks.java +++ b/src/main/java/net/minecraftforge/common/ForgeHooks.java @@ -52,6 +52,7 @@ import net.minecraft.core.NonNullList; import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.ClickEvent; import net.minecraft.network.chat.Component; @@ -101,9 +102,13 @@ import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.ListTag; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.common.custom.DiscardedPayload; import net.minecraft.network.syncher.EntityDataSerializer; import net.minecraft.network.protocol.game.ClientGamePacketListener; import net.minecraft.network.protocol.game.ClientboundBlockUpdatePacket; +import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; +import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket; import net.minecraft.stats.Stats; import net.minecraft.world.phys.Vec3; import net.minecraft.world.level.block.entity.FurnaceBlockEntity; @@ -161,7 +166,7 @@ import net.minecraftforge.fml.ModLoader; import net.minecraftforge.fml.config.ConfigTracker; import net.minecraftforge.fml.util.thread.EffectiveSide; import net.minecraftforge.network.ConnectionType; -import net.minecraftforge.network.ICustomPacket; +import net.minecraftforge.network.ForgePayload; import net.minecraftforge.network.NetworkContext; import net.minecraftforge.network.NetworkDirection; import net.minecraftforge.network.NetworkInitialization; @@ -1111,19 +1116,51 @@ public final class ForgeHooks { } @ApiStatus.Internal - public static boolean onCustomPayload(ICustomPacket packet, Connection connection) { + public static StreamCodec getCustomPayloadCodec(ResourceLocation id, int max) { + var channel = NetworkRegistry.findTarget(id); + if (channel == null) + return DiscardedPayload.codec(id, max); + + return StreamCodec.ofMember( + (value, buf) -> { + value.encoder().accept(buf); + }, + (buf) -> { + int len = buf.readableBytes(); + if (len < 0 || len > max) + throw new IllegalArgumentException("Payload may not be larger then " + max + " bytes"); + return ForgePayload.create(id, buf.wrap(buf.readBytes(len))); + } + ); + } + + @ApiStatus.Internal + public static boolean onCustomPayload(CustomPacketPayload payload, Connection connection) { + var context = new CustomPayloadEvent.Context(connection); + return onCustomPayload(new CustomPayloadEvent(payload.type().id(), payload, context, 0)); + } + + @ApiStatus.Internal + public static boolean onCustomPayload(ClientboundCustomQueryPacket packet, Connection connection) { + var context = new CustomPayloadEvent.Context(connection); + return onCustomPayload(new CustomPayloadEvent(packet.payload().id(), packet.payload(), context, packet.transactionId())); + } + + @ApiStatus.Internal + public static boolean onCustomPayload(ServerboundCustomQueryAnswerPacket packet, Connection connection) { + var context = new CustomPayloadEvent.Context(connection); + return onCustomPayload(new CustomPayloadEvent(NetworkInitialization.LOGIN_NAME, packet.payload(), context, packet.transactionId())); + } + + @ApiStatus.Internal + public static boolean onCustomPayload(CustomPayloadEvent event) { + var connection = event.getSource().getConnection(); var expectedSide = connection.getReceiving() == PacketFlow.CLIENTBOUND ? LogicalSide.CLIENT : LogicalSide.SERVER; if (expectedSide != EffectiveSide.get()) { connection.disconnect(Component.literal("Illegal packet received, terminating connection")); return false; } - var context = new CustomPayloadEvent.Context(connection); - return onCustomPayload(new CustomPayloadEvent(packet, context)); - } - - @ApiStatus.Internal - public static boolean onCustomPayload(CustomPayloadEvent event) { var channel = NetworkRegistry.findTarget(event.getChannel()); if (channel != null && channel.dispatch(event)) return true; @@ -1146,13 +1183,12 @@ public final class ForgeHooks { LOGGER.info("Connected to a modded server."); } - @SuppressWarnings("unchecked") @ApiStatus.Internal public static Packet getEntitySpawnPacket(Entity entity) { if (!(entity instanceof IEntityAdditionalSpawnData add)) throw new IllegalArgumentException(entity.getClass() + " is not an instance of " + IEntityAdditionalSpawnData.class); - return (Packet)NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity)); + return NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity)); } @ApiStatus.Internal diff --git a/src/main/java/net/minecraftforge/common/util/PacketLogger.java b/src/main/java/net/minecraftforge/common/util/PacketLogger.java index 26051c915c..4d0276e12c 100644 --- a/src/main/java/net/minecraftforge/common/util/PacketLogger.java +++ b/src/main/java/net/minecraftforge/common/util/PacketLogger.java @@ -10,11 +10,17 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; +import io.netty.buffer.Unpooled; import net.minecraft.network.Connection; +import net.minecraft.network.PacketListener; +import net.minecraft.network.ProtocolInfo; 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.configuration.ClientboundFinishConfigurationPacket; -import net.minecraftforge.network.ICustomPacket; +import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; +import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket; /* * This is a utility class that just dumps packets to the logger. @@ -33,20 +39,44 @@ public class PacketLogger { } public void send(Packet packet) { - common(connection.getReceiving(), connection.getSending(), packet); + common(connection.getReceiving(), connection.getSending(), connection.getOutputboundProtocolInfo(), packet); } public void recv(Packet packet) { - common(connection.getReceiving(), connection.getReceiving(), packet); + common(connection.getReceiving(), connection.getReceiving(), connection.getInboundProtocolInfo(), packet); } - private void common(PacketFlow side, PacketFlow flow, Packet packet) { + private void common(PacketFlow side, PacketFlow flow, ProtocolInfo protocol, Packet packet) { if (!enabled) return; if (packet instanceof ClientboundFinishConfigurationPacket) enabled = false; - if (packet instanceof ICustomPacket custom && custom.getInternalData() != null) { - LOGGER.info(MARKER, "{} {} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), custom.getName(), HexDumper.dump(custom.getInternalData())); + String channel = null; + String hex = null; + switch (packet) { + case ClientboundCustomPayloadPacket custom -> { + channel = custom.payload().type().id().toString(); + hex = hex(protocol, packet); + } + case ServerboundCustomPayloadPacket custom -> { + channel = custom.payload().type().id().toString(); + hex = hex(protocol, packet); + } + case ClientboundCustomQueryPacket custom -> { + channel = custom.payload().id().toString() + " id " + custom.transactionId(); + hex = hex(protocol, packet); + } + case ServerboundCustomQueryAnswerPacket custom -> { + channel = packet.getClass().getName() + " id " + custom.transactionId(); + hex = hex(protocol, packet); + } + default -> {} + } + + if (channel != null && hex != null) { + LOGGER.info(MARKER, "{} {} {} {}\n{}", side(side), dir(flow), packet.getClass().getName(), channel, hex); + } else if (channel != null) { + LOGGER.info(MARKER, "{} {} {} {}", side(side), dir(flow), packet.getClass().getName(), channel); } else { LOGGER.info(MARKER, "{} {} {}", side(side), dir(flow), packet.getClass().getName()); } @@ -58,4 +88,11 @@ public class PacketLogger { private static String dir(PacketFlow flow) { return flow == PacketFlow.CLIENTBOUND ? "S->C" : "C->S"; } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + private static String hex(ProtocolInfo protocol, Packet packet) { + var buf = Unpooled.buffer(); + protocol.codec().encode(buf, packet); + return HexDumper.dump(buf); + } } diff --git a/src/main/java/net/minecraftforge/event/network/CustomPayloadEvent.java b/src/main/java/net/minecraftforge/event/network/CustomPayloadEvent.java index 6f514d5615..302ee98341 100644 --- a/src/main/java/net/minecraftforge/event/network/CustomPayloadEvent.java +++ b/src/main/java/net/minecraftforge/event/network/CustomPayloadEvent.java @@ -16,7 +16,7 @@ import net.minecraft.server.network.ServerGamePacketListenerImpl; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; import net.minecraftforge.eventbus.api.Event; -import net.minecraftforge.network.ICustomPacket; +import net.minecraftforge.network.ForgePayload; import net.minecraftforge.common.util.LogicalSidedProvider; import org.jetbrains.annotations.Nullable; @@ -26,27 +26,30 @@ import java.util.concurrent.CompletableFuture; // But also expose it as a generic listener event for anyone who cares about it but is outside out channel control system. public class CustomPayloadEvent extends Event { private final ResourceLocation channel; - private final FriendlyByteBuf payload; + private final Object payload; + private final FriendlyByteBuf data; private final Context source; private final int loginIndex; - public CustomPayloadEvent(ResourceLocation channel, FriendlyByteBuf payload, Context source, int loginIndex) { + public CustomPayloadEvent(ResourceLocation channel, Object payload, Context source, int loginIndex) { this.channel = channel; this.payload = payload; + this.data = payload instanceof ForgePayload forge ? forge.data() : null; this.source = source; this.loginIndex = loginIndex; } - public CustomPayloadEvent(ICustomPacket payload, Context source) { - this(payload.getName(), payload.getInternalData(), source, payload.getIndex()); - } - public ResourceLocation getChannel() { return this.channel; } + @Nullable public FriendlyByteBuf getPayload() { - return payload; + return data; + } + + public Object getPayloadObject() { + return this.payload; } public int getLoginIndex() { diff --git a/src/main/java/net/minecraftforge/network/Channel.java b/src/main/java/net/minecraftforge/network/Channel.java index 60babc3597..1aee3cbdd4 100644 --- a/src/main/java/net/minecraftforge/network/Channel.java +++ b/src/main/java/net/minecraftforge/network/Channel.java @@ -7,9 +7,15 @@ package net.minecraftforge.network; import java.util.Objects; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; + import net.minecraft.network.Connection; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.resources.ResourceLocation; @@ -17,6 +23,8 @@ import net.minecraftforge.event.network.CustomPayloadEvent; import net.minecraftforge.network.packets.LoginWrapper; public abstract class Channel { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Marker MARKER = MarkerManager.getMarker("CHANNEL"); protected final NetworkInstance instance; protected Channel(NetworkInstance instance) { @@ -27,6 +35,15 @@ public abstract class Channel { return instance.getChannelName(); } + /** + * Retrieves the channel name to use for the specified packet + * This typically is the main channel's name, but in some cases + * you want to specify a custom one. If your channel has {@link NetworkInstance#addChild(ResourceLocation) children} + */ + ResourceLocation getName(MSG packet) { + return getName(); + } + public int getProtocolVersion() { return instance.getNetworkProtocolVersion(); } @@ -35,12 +52,11 @@ public abstract class Channel { * Returns true if the channel is present in the given connection. */ public boolean isRemotePresent(Connection connection) { - return NetworkContext.get(connection).getRemoteChannels().contains(getName()); + return instance.isRemotePresent(connection); } public abstract void encode(FriendlyByteBuf out, MSG message); - // Package private so we can call from ourselves. protected Packet toVanillaPacket(Connection connection, MSG message) { var protocol = connection.getProtocol(); var handler = switch (protocol) { @@ -55,7 +71,7 @@ public abstract class Channel { if (protocol == ConnectionProtocol.LOGIN && this != NetworkInitialization.LOGIN && connection.getSending() == PacketFlow.SERVERBOUND) return NetworkInitialization.LOGIN.toVanillaPacket(connection, new LoginWrapper(this, message)); - return handler.buildPacket(connection.getSending(), this, message).getThis(); + return handler.buildPacket(connection.getSending(), this, message); } public void send(MSG msg, Connection connection) { @@ -74,13 +90,30 @@ public abstract class Channel { * @param The type of the message */ public void send(MSG msg, PacketDistributor.PacketTarget target) { - target.send(target.direction().buildPacket(this, msg).getThis()); + target.send(target.direction().buildPacket(this, msg)); } public void reply(MSG msg, CustomPayloadEvent.Context context) { send(msg, context.getConnection()); } + protected void validate(Object id, Connection con, NetworkProtocol protocol, PacketFlow direction, boolean sending) { + var actualD = sending ? con.getSending() : con.getReceiving(); + var actualP = con.getProtocol(); + var expectedP = protocol == null ? actualP : protocol.toVanilla(); + var expectedD = direction == null ? actualD : direction; + + if (expectedP != actualP || expectedD != actualD) { + var error = "Illegal packet " + (sending ? "sent" : "received") + ", terminating connection. " + id + " expected " + + expectedD + " " + expectedP + " but was " + + actualD + " " + actualP; + + LOGGER.error(MARKER, error); + con.disconnect(Component.literal(error)); + throw new IllegalStateException(error); + } + } + @FunctionalInterface public static interface VersionTest { public static final VersionTest ACCEPT_MISSING = (status, version) -> status == Status.MISSING; diff --git a/src/main/java/net/minecraftforge/network/ChannelBuildable.java b/src/main/java/net/minecraftforge/network/ChannelBuildable.java new file mode 100644 index 0000000000..aeb13950c0 --- /dev/null +++ b/src/main/java/net/minecraftforge/network/ChannelBuildable.java @@ -0,0 +1,14 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network; + +public interface ChannelBuildable { + /* + * This will build the entire channel, locking any future modifications from happening. + * This should be the LAST call in your builder chain and can only be called once. + */ + Channel build(); +} diff --git a/src/main/java/net/minecraftforge/network/ChannelBuilder.java b/src/main/java/net/minecraftforge/network/ChannelBuilder.java index 768df10af5..c729c9f1a6 100644 --- a/src/main/java/net/minecraftforge/network/ChannelBuilder.java +++ b/src/main/java/net/minecraftforge/network/ChannelBuilder.java @@ -14,8 +14,10 @@ import java.util.function.Supplier; import io.netty.util.AttributeKey; import net.minecraft.network.Connection; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.network.Channel.VersionTest; +import net.minecraftforge.network.payload.PayloadConnection; /** * Builder for constructing impl channels using a builder style API. @@ -27,6 +29,7 @@ public class ChannelBuilder { private Channel.VersionTest serverAcceptedVersions; private Map, Function> attributes = new HashMap<>(); private Consumer connectionHandler; + private boolean registerSelf = true; /** * Creates a new channel builder, The name of the channel must be unique. @@ -171,21 +174,17 @@ public class ChannelBuilder { * @return the {@link NetworkInstance} */ private NetworkInstance createNetworkInstance() { - if (NetworkRegistry.lock) { - NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "Attempted to register channel {} even though registry phase is over", name); - throw new IllegalArgumentException("Registration of impl channels is locked"); - } - if (NetworkRegistry.instances.containsKey(name)) { - NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, "NetworkDirection channel {} already registered.", name); - throw new IllegalArgumentException("NetworkDirection Channel {"+ name +"} already registered"); - } - - var networkInstance = new NetworkInstance(name, networkProtocolVersion, + var instance = new NetworkInstance(name, networkProtocolVersion, getClientAcceptedVersions(), getServerAcceptedVersions(), attributes, connectionHandler ); - NetworkRegistry.instances.put(name, networkInstance); - return networkInstance; + + NetworkRegistry.register(instance); + + if (registerSelf) + instance.addChild(name); + + return instance; } @@ -203,7 +202,7 @@ public class ChannelBuilder { * @return A new {@link SimpleChannel} */ public SimpleChannel simpleChannel() { - return new SimpleChannel(createNetworkInstance()); + return channel(SimpleChannel::new); } /** @@ -211,6 +210,23 @@ public class ChannelBuilder { * @return A new {@link EventNetworkChannel} */ public EventNetworkChannel eventNetworkChannel() { - return new EventNetworkChannel(createNetworkInstance()); + return channel(EventNetworkChannel::new); + } + + /** + * Build a new {@link PayloadChannel} with this builder's configuration. + * @return A new {@link PayloadConnection PayloadConnection<CustomPacketPayload>} + */ + public PayloadConnection payloadChannel() { + this.registerSelf = false; + return channel(PayloadChannel::builder); + } + + /** + * Registers this channel with the {@link NetworkManager} and calls the supplied Function. + * This is meant to allow modders to build their own Channel implementations. + */ + public C channel(Function factory) { + return factory.apply(createNetworkInstance()); } } \ No newline at end of file diff --git a/src/main/java/net/minecraftforge/network/ChannelListManager.java b/src/main/java/net/minecraftforge/network/ChannelListManager.java index 02fa2f91af..7b2fe2f1bd 100644 --- a/src/main/java/net/minecraftforge/network/ChannelListManager.java +++ b/src/main/java/net/minecraftforge/network/ChannelListManager.java @@ -6,10 +6,11 @@ package net.minecraftforge.network; import com.mojang.logging.LogUtils; -import io.netty.buffer.Unpooled; import net.minecraft.ResourceLocationException; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; import net.minecraft.resources.ResourceLocation; import net.minecraftforge.event.ForgeEventFactory; import net.minecraftforge.event.network.ChannelRegistrationChangeEvent; @@ -19,44 +20,70 @@ import org.jetbrains.annotations.ApiStatus; import org.slf4j.Logger; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashSet; +import java.util.List; @ApiStatus.Internal // TODO: Decide what I want to have public, Right now it should just be add/remove channels. public class ChannelListManager { private static final Logger LOGGER = LogUtils.getLogger(); - static final EventNetworkChannel REGISTER = ChannelBuilder - .named("minecraft:register") - .optional() - .eventNetworkChannel() - .addListener(ChannelListManager::registerListener); - static final EventNetworkChannel UNREGISTER = ChannelBuilder - .named("minecraft:unregister") - .optional() - .eventNetworkChannel() - .addListener(ChannelListManager::unregisterListener); - private static void registerListener(CustomPayloadEvent evt) { - var ctx = evt.getSource(); - updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.REGISTER); + public static final ResourceLocation NAME = new ResourceLocation("forge", "channel_registration"); + + static final Channel CHANNEL = ChannelBuilder + .named(NAME) + .optional() + .payloadChannel() + .any() + .bidirectional() + .add(Register.TYPE, Register.CODEC, ChannelListManager::register) + .add(Unregister.TYPE, Unregister.CODEC, ChannelListManager::unregister) + .build(); + + private record Register(List channels) implements CustomPacketPayload { + private static Type TYPE = CustomPacketPayload.createType("minecraft:register"); + private static StreamCodec CODEC = StreamCodec.of( + (buf, v) -> encode(buf, v.channels), + buf -> new Register(decode(buf)) + ); + + @Override + public Type type() { + return TYPE; + } + } + + private record Unregister(List channels) implements CustomPacketPayload { + private static Type TYPE = CustomPacketPayload.createType("minecraft:unregister"); + private static StreamCodec CODEC = StreamCodec.of( + (buf, v) -> encode(buf, v.channels), + buf -> new Unregister(decode(buf)) + ); + + @Override + public Type type() { + return TYPE; + } + } + + private static void register(Register payload, CustomPayloadEvent.Context ctx) { + updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.REGISTER); ctx.setPacketHandled(true); // Send the client's channels to the server whenever asked. If we're in the login state then our login wrapper will unwrap for us. if (ctx.isClientSide()) addChannels(ctx.getConnection()); } - private static void unregisterListener(CustomPayloadEvent evt) { - var ctx = evt.getSource(); - updateFrom(ctx, evt.getPayload(), ChannelRegistrationChangeEvent.Type.UNREGISTER); + private static void unregister(Unregister payload, CustomPayloadEvent.Context ctx) { + updateFrom(ctx, payload.channels(), ChannelRegistrationChangeEvent.Type.UNREGISTER); ctx.setPacketHandled(true); } public static void addChannels(Connection connection) { - addChannels(connection, NetworkRegistry.buildChannelVersions().keySet().stream() - .filter(rl -> !"minecraft".equals(rl.getNamespace())) - .toList() - ); + addChannels(connection, NetworkRegistry.buildRegisterList()); } public static void addChannels(Connection connection, ResourceLocation... channels) { @@ -65,12 +92,14 @@ public class ChannelListManager { public static void addChannels(Connection connection, Collection channels) { var list = NetworkContext.get(connection); - var toSend = new HashSet(); + var toSend = new HashSet(); for (var channel : channels) { if (list.sentChannels.add(channel)) - toSend.add(channel); + toSend.add(channel.toString()); } - sendChannels(REGISTER, connection, toSend); + + if (!toSend.isEmpty()) + CHANNEL.send(new Register(toSend.stream().sorted().toList()), connection); } public static void removeChannels(Connection connection, ResourceLocation... channels) { @@ -79,31 +108,27 @@ public class ChannelListManager { public static void removeChannels(Connection connection, Collection channels) { var list = NetworkContext.get(connection); - var toSend = new HashSet(); + var toSend = new HashSet(); for (var channel : channels) { if (list.sentChannels.remove(channel)) - toSend.add(channel); + toSend.add(channel.toString()); } - sendChannels(UNREGISTER, connection, toSend); + + if (!toSend.isEmpty()) + CHANNEL.send(new Unregister(toSend.stream().toList()), connection); } - private static void sendChannels(EventNetworkChannel channel, Connection connection, Collection channels) { - if (channels.isEmpty()) - return; - - var buf = new FriendlyByteBuf(Unpooled.buffer()); + private static void encode(FriendlyByteBuf buf, List channels) { for (var c : channels) { buf.writeBytes(c.toString().getBytes(StandardCharsets.UTF_8)); buf.writeByte(0); } - - channel.send(buf, connection); } - private static void updateFrom(CustomPayloadEvent.Context source, FriendlyByteBuf buffer, final ChannelRegistrationChangeEvent.Type changeType) { + private static List decode(FriendlyByteBuf buffer) { byte[] data = new byte[Math.max(buffer.readableBytes(), 0)]; buffer.readBytes(data); - var channels = new HashSet(); + var channels = new ArrayList(); int last = 0; for (int cur = 0; cur < data.length; cur++) { @@ -117,6 +142,10 @@ public class ChannelListManager { if (last < data.length) channels.add(new String(data, last, data.length - last, StandardCharsets.UTF_8)); + return Collections.unmodifiableList(channels); + } + + private static void updateFrom(CustomPayloadEvent.Context source, List channels, final ChannelRegistrationChangeEvent.Type changeType) { var changed = new HashSet(); for (var channel : channels) { // It also says nothing about the format of channels so ignore bad channels. @@ -142,7 +171,7 @@ public class ChannelListManager { if (fire) { var target = NetworkRegistry.findTarget(channel); if (target != null) - target.registrationChange(changeType == ChannelRegistrationChangeEvent.Type.REGISTER); + target.registrationChange(channel, changeType == ChannelRegistrationChangeEvent.Type.REGISTER); } } } diff --git a/src/main/java/net/minecraftforge/network/ForgePacketHandler.java b/src/main/java/net/minecraftforge/network/ForgePacketHandler.java index 66b83ede91..3cc624ec75 100644 --- a/src/main/java/net/minecraftforge/network/ForgePacketHandler.java +++ b/src/main/java/net/minecraftforge/network/ForgePacketHandler.java @@ -73,7 +73,7 @@ public class ForgePacketHandler { } void handleLoginWrapper(LoginWrapper msg, CustomPayloadEvent.Context ctx) { - ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.name(), msg.data(), ctx, -1)); + ForgeHooks.onCustomPayload(new CustomPayloadEvent(msg.name(), ForgePayload.create(msg.name(), msg.data()), ctx, -1)); } void handleClientAck(Acknowledge msg, CustomPayloadEvent.Context ctx) { diff --git a/src/main/java/net/minecraftforge/network/ForgePayload.java b/src/main/java/net/minecraftforge/network/ForgePayload.java new file mode 100644 index 0000000000..4f7203f791 --- /dev/null +++ b/src/main/java/net/minecraftforge/network/ForgePayload.java @@ -0,0 +1,43 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network; + +import java.util.function.Consumer; + +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.login.custom.CustomQueryAnswerPayload; +import net.minecraft.network.protocol.login.custom.CustomQueryPayload; +import net.minecraft.resources.ResourceLocation; + +@ApiStatus.Internal +public record ForgePayload ( + ResourceLocation id, + @Nullable + FriendlyByteBuf data, + Consumer encoder +) implements CustomPacketPayload, CustomQueryPayload, CustomQueryAnswerPayload { + public static ForgePayload create(ResourceLocation id, FriendlyByteBuf data) { + return new ForgePayload(id, data, buf -> buf.writeBytes(data.slice())); + } + + public static ForgePayload create(ResourceLocation id, Consumer encoder) { + return new ForgePayload(id, null, encoder); + } + + @Override + public void write(FriendlyByteBuf buf) { + encoder.accept(buf); + } + + @Override + public Type type() { + return new Type<>(this.id()); + } +} diff --git a/src/main/java/net/minecraftforge/network/ICustomPacket.java b/src/main/java/net/minecraftforge/network/ICustomPacket.java deleted file mode 100644 index daaf111d0f..0000000000 --- a/src/main/java/net/minecraftforge/network/ICustomPacket.java +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (c) Forge Development LLC and contributors - * SPDX-License-Identifier: LGPL-2.1-only - */ - -package net.minecraftforge.network; - -import net.minecraft.network.protocol.Packet; - -import org.jetbrains.annotations.Nullable; - -import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.resources.ResourceLocation; - -public interface ICustomPacket> { - /** - * Returns a unsafe reference to this packet's internal data. - * Any modifications to this buffer will be reflected in the main buffer. - */ - @Nullable - FriendlyByteBuf getInternalData(); - - ResourceLocation getName(); - - int getIndex(); - - @SuppressWarnings("unchecked") - default T getThis() { - return (T)this; - } -} diff --git a/src/main/java/net/minecraftforge/network/NetworkDirection.java b/src/main/java/net/minecraftforge/network/NetworkDirection.java index ee0abfb423..8199fd0a91 100644 --- a/src/main/java/net/minecraftforge/network/NetworkDirection.java +++ b/src/main/java/net/minecraftforge/network/NetworkDirection.java @@ -8,6 +8,7 @@ package net.minecraftforge.network; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.PacketListener; import net.minecraft.network.RegistryFriendlyByteBuf; public record NetworkDirection(NetworkProtocol protocol, PacketFlow direction) { @@ -18,7 +19,7 @@ public record NetworkDirection(NetworkProtocol pro public static final NetworkDirection CONFIGURATION_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.CLIENTBOUND); public static final NetworkDirection CONFIGURATION_TO_SERVER = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.SERVERBOUND); - public , MSG> ICustomPacket buildPacket(Channel channel, MSG packet) { + public Packet buildPacket(Channel channel, MSG packet) { return this.protocol().buildPacket(this.direction(), channel, packet); } } diff --git a/src/main/java/net/minecraftforge/network/NetworkInitialization.java b/src/main/java/net/minecraftforge/network/NetworkInitialization.java index 4be8c83254..143c22b80b 100644 --- a/src/main/java/net/minecraftforge/network/NetworkInitialization.java +++ b/src/main/java/net/minecraftforge/network/NetworkInitialization.java @@ -23,7 +23,7 @@ public final class NetworkInitialization { private static final Marker MARKER = MarkerManager.getMarker("FORGE_NETWORK"); public static final ResourceLocation LOGIN_NAME = new ResourceLocation("forge", "login"); private static final ResourceLocation HANDSHAKE_NAME = new ResourceLocation("forge", "handshake"); - private static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "network"); + //private static final ResourceLocation PLAY_NAME = new ResourceLocation("forge", "network"); public static final AttributeKey CONTEXT = AttributeKey.newInstance(HANDSHAKE_NAME.toString()); public static final SimpleChannel LOGIN = ChannelBuilder @@ -62,7 +62,7 @@ public final class NetworkInitialization { public static final SimpleChannel PLAY = CONFIG;; public static void init() { - for (var channel : new Channel[]{ LOGIN, CONFIG, PLAY, ChannelListManager.REGISTER, ChannelListManager.UNREGISTER}) + for (var channel : new Channel[]{ LOGIN, CONFIG, PLAY, ChannelListManager.CHANNEL}) LOGGER.debug(MARKER, "Registering Network {} v{}", channel.getName(), channel.getProtocolVersion()); } diff --git a/src/main/java/net/minecraftforge/network/NetworkInstance.java b/src/main/java/net/minecraftforge/network/NetworkInstance.java index c23dd2f2f0..c58ca899c4 100644 --- a/src/main/java/net/minecraftforge/network/NetworkInstance.java +++ b/src/main/java/net/minecraftforge/network/NetworkInstance.java @@ -14,7 +14,9 @@ import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventListener; import net.minecraftforge.network.Channel.VersionTest; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.function.Consumer; import java.util.function.Function; import org.jetbrains.annotations.ApiStatus; @@ -38,6 +40,7 @@ public final class NetworkInstance { final Map, Function> attributes; final Consumer channelHandler; final ServerStatusPing.ChannelData pingData; + private final Set ids = new HashSet<>(); NetworkInstance(ResourceLocation channelName, int networkProtocolVersion, VersionTest clientAcceptedVersions, VersionTest serverAcceptedVersions, @@ -73,6 +76,16 @@ public final class NetworkInstance { return event.getSource().getPacketHandled(); } + /** + * Registers another name that will have its CustomPayloadEvents redirected to this channel. + * Like the main name, this must be unique across all channels. + */ + public NetworkInstance addChild(ResourceLocation name) { + NetworkRegistry.register(this, name); + this.ids.add(name); + return this; + } + ResourceLocation getChannelName() { return channelName; } @@ -81,7 +94,12 @@ public final class NetworkInstance { return networkProtocolVersion; } - void registrationChange(boolean registered) { + void registrationChange(ResourceLocation name, boolean registered) { // TODO: Expose to listeners? } + + boolean isRemotePresent(Connection con) { + var channels = NetworkContext.get(con).getRemoteChannels(); + return channels.containsAll(ids); + } } diff --git a/src/main/java/net/minecraftforge/network/NetworkProtocol.java b/src/main/java/net/minecraftforge/network/NetworkProtocol.java index b50632c06c..d1b28c1722 100644 --- a/src/main/java/net/minecraftforge/network/NetworkProtocol.java +++ b/src/main/java/net/minecraftforge/network/NetworkProtocol.java @@ -9,17 +9,16 @@ 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.DiscardedPayload; import java.util.function.Consumer; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.PacketListener; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.protocol.login.ClientboundCustomQueryPacket; import net.minecraft.network.protocol.login.ServerboundCustomQueryAnswerPacket; -import net.minecraft.network.protocol.login.custom.DiscardedQueryAnswerPayload; -import net.minecraft.network.protocol.login.custom.DiscardedQueryPayload; +import net.minecraft.resources.ResourceLocation; public class NetworkProtocol { public static final NetworkProtocol PLAY = new NetworkProtocol<>(ConnectionProtocol.PLAY); @@ -41,23 +40,26 @@ public class NetworkProtocol { return this.protocol; } + public Packet buildPacket(PacketFlow direction, Channel channel, MSG packet) { + return buildPacket(direction, channel.getName(packet), buf -> channel.encode(buf, packet)); + } + @SuppressWarnings("unchecked") - public , MSG> ICustomPacket buildPacket(PacketFlow direction, Channel channel, MSG packet) { - var name = channel.getName(); - var encoder = (Consumer)(buf -> channel.encode(buf, packet)); + public Packet buildPacket(PacketFlow direction, ResourceLocation name, Consumer encoder) { + var payload = ForgePayload.create(name, (Consumer)encoder); switch (this.protocol) { case PLAY, CONFIGURATION: if (direction == PacketFlow.CLIENTBOUND) - return (ICustomPacket)new ClientboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder)); + return (Packet)new ClientboundCustomPayloadPacket(payload); else - return (ICustomPacket)new ServerboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder)); + return (Packet)new ServerboundCustomPayloadPacket(payload); case LOGIN: if (direction == PacketFlow.CLIENTBOUND) - return (ICustomPacket)new ClientboundCustomQueryPacket(0, new DiscardedQueryPayload(name, null, encoder)); + return (Packet)new ClientboundCustomQueryPacket(0, payload); else - return (ICustomPacket)new ServerboundCustomQueryAnswerPacket(0, new DiscardedQueryAnswerPayload(null, encoder)); + return (Packet)new ServerboundCustomQueryAnswerPacket(0, payload); default: throw new IllegalArgumentException("Invalid protocol, shouldn't be possible as this is a private class.."); diff --git a/src/main/java/net/minecraftforge/network/NetworkRegistry.java b/src/main/java/net/minecraftforge/network/NetworkRegistry.java index 6361bcce72..586a52de3a 100644 --- a/src/main/java/net/minecraftforge/network/NetworkRegistry.java +++ b/src/main/java/net/minecraftforge/network/NetworkRegistry.java @@ -41,25 +41,28 @@ public class NetworkRegistry { static final Logger LOGGER = LogManager.getLogger(); static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY"); - static Map instances = Collections.synchronizedMap(new HashMap<>()); + private static Map instances = Collections.synchronizedMap(new HashMap<>()); + private static Map byName = Collections.synchronizedMap(new HashMap<>()); public static boolean acceptsVanillaClientConnections() { - return (instances.isEmpty() || listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty()) && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty(); + return listRejectedVanillaMods(n -> n.clientAcceptedVersions).isEmpty() && DataPackRegistriesHooks.getSyncedCustomRegistries().isEmpty(); } public static boolean canConnectToVanillaServer() { - return instances.isEmpty() || listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty(); + return listRejectedVanillaMods(n -> n.serverAcceptedVersions).isEmpty(); } @Nullable public static NetworkInstance findTarget(ResourceLocation resourceLocation) { - return instances.get(resourceLocation); + return byName.get(resourceLocation); } static Map buildChannelVersionsForListPing() { - return instances.entrySet().stream() - .filter(p -> !p.getKey().getNamespace().equals("forge")) - .collect(Collectors.toMap(e -> e.getKey(), e -> e.getValue().pingData)); + var ret = new HashMap(); + for (var channel : instances.values()) { + ret.put(channel.getChannelName(), channel.pingData); + } + return ret; } static List listRejectedVanillaMods(Function testFunction) { @@ -86,7 +89,8 @@ public class NetworkRegistry { Set missing = new HashSet<>(); Map results = new HashMap<>(); - instances.forEach((name, net) -> { + for (var net : instances.values()) { + var name = net.getChannelName(); VersionTest test = fromClient ? net.clientAcceptedVersions : net.serverAcceptedVersions; var status = VersionTest.Status.MISSING; @@ -105,7 +109,7 @@ public class NetworkRegistry { else results.put(name, new NetworkMismatchData.Version(Integer.toString(version), Integer.toString(net.getNetworkProtocolVersion()))); } - }); + } if (!results.isEmpty() || !missing.isEmpty()) { LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number", @@ -176,7 +180,45 @@ public class NetworkRegistry { public static Map buildChannelVersions() { var ret = new Object2IntOpenHashMap(instances.size()); - instances.forEach((k, v) -> ret.put(k, v.getNetworkProtocolVersion())); + for (var net : instances.values()) { + ret.put(net.getChannelName(), net.getNetworkProtocolVersion()); + } return ret; } + + static List buildRegisterList() { + var ret = new ArrayList(byName.keySet().size()); + for (var name : byName.keySet()) + if (!"minecraft".equals(name.getNamespace())) + ret.add(name); + return ret; + } + + static void register(NetworkInstance instance, ResourceLocation name) { + checkLock(instance); + if (NetworkRegistry.byName.containsKey(name)) + error("Payload name " + name + " already registered."); + + byName.put(name, instance); + } + + static void register(NetworkInstance instance) { + checkLock(instance); + + var name = instance.getChannelName(); + if (NetworkRegistry.instances.containsKey(name)) + error("Channel " + name + " already registered."); + + instances.put(name, instance); + } + + private static void checkLock(NetworkInstance instance) { + if (NetworkRegistry.lock) + error("Attempted to register channel " + instance.getChannelName() + " even though registry phase is over"); + } + + private static void error(String message) { + NetworkRegistry.LOGGER.error(NetworkRegistry.NETREGISTRY, message); + throw new IllegalArgumentException(message); + } } diff --git a/src/main/java/net/minecraftforge/network/PayloadChannel.java b/src/main/java/net/minecraftforge/network/PayloadChannel.java new file mode 100644 index 0000000000..9002cfa132 --- /dev/null +++ b/src/main/java/net/minecraftforge/network/PayloadChannel.java @@ -0,0 +1,241 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network; + +import io.netty.util.AttributeKey; +import net.minecraft.network.Connection; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.Packet; +import net.minecraft.network.protocol.PacketFlow; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.event.network.CustomPayloadEvent; +import net.minecraftforge.event.network.CustomPayloadEvent.Context; +import net.minecraftforge.network.simple.handler.SimplePacket; +import net.minecraftforge.network.payload.PayloadConnection; +import net.minecraftforge.network.payload.PayloadFlow; +import net.minecraftforge.network.payload.PayloadProtocol; +import net.minecraftforge.network.payload.handler.PayloadHandlerFlow; +import net.minecraftforge.network.payload.handler.PayloadHandlerProtocol; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.function.BiConsumer; + +final class PayloadChannel extends Channel { + private static final Logger LOGGER = LogManager.getLogger(); + private static final Marker MARKER = MarkerManager.getMarker("PAYLOAD_CHANNEL"); + private final Map> payloads; + + @ApiStatus.Internal + private PayloadChannel(Builder builder) { + super(builder.instance); + builder.instance.addListener(this::onPacketReceived); + this.payloads = Collections.unmodifiableMap(builder.payloads); + } + + static PayloadConnection builder(NetworkInstance instance) { + return new Builder(instance); + } + + private final static class Builder implements PayloadConnection { + private final NetworkInstance instance; + private boolean built = false; + private final Map> payloads = new HashMap<>(); + + private Builder(NetworkInstance instance) { + this.instance = instance; + } + + @Override + public PayloadProtocol protocol(NetworkProtocol protocol) { + return new Protocol<>(new BuilderContext(this).protocol(protocol)); + } + + @Override + public > PayloadHandlerProtocol protocol(AttributeKey context, NetworkProtocol protocol) { + return new HandlerProtocol<>(context, new BuilderContext(this).protocol(protocol)); + } + + private void add(Message msg) { + if (this.payloads.put(msg.type().id(), msg) != null) + throw new IllegalArgumentException("Packet with type " + msg.type().id() + " already registered"); + + this.instance.addChild(msg.type().id()); + } + + private void checkBuilt() { + if (built) + throw new IllegalStateException("PayloadChannel builder is fully built, can not modify it any more"); + } + + public PayloadChannel build() { + checkBuilt(); + return new PayloadChannel(this); + } + + private record BuilderContext(Builder builder, @Nullable NetworkProtocol protocol, @Nullable PacketFlow flow) { + public BuilderContext(Builder builder) { + this(builder, null, null); + } + + public BuilderContext protocol(NetworkProtocol protocol) { + return new BuilderContext<>(builder, protocol, flow); + } + + private BuilderContext flow(@Nullable PacketFlow flow) { + return new BuilderContext<>(builder, protocol, flow); + } + } + private record Protocol(BuilderContext ctx) implements ProtocolFactory, PayloadProtocol { + @Override + public PayloadFlow flow(@Nullable PacketFlow flow) { + return new Flow<>(ctx.flow(flow)); + } + } + + private record HandlerProtocol>( + AttributeKey key, + BuilderContext ctx + ) implements ProtocolFactory, PayloadHandlerProtocol { + @Override + public PayloadHandlerFlow flow(@Nullable PacketFlow flow) { + return new HandlerFlow<>(key, ctx.flow(flow)); + } + } + + private record Flow(BuilderContext ctx) implements ProtocolFactory, PayloadFlow { + @Override + public Flow add(Type type, StreamCodec codec, BiConsumer consumer) { + ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer)); + return this; + } + + @Override + public PayloadFlow flow(@Nullable PacketFlow flow) { + return new Flow<>(ctx.flow(flow)); + } + } + + private record HandlerFlow>( + AttributeKey key, + BuilderContext ctx + ) implements ProtocolFactory, PayloadHandlerFlow { + @Override + public PayloadHandlerFlow add(Type type, StreamCodec codec) { + return this.add(type, codec, (msg, ctx) -> { + var inst = ctx.getConnection().channel().attr(key).get(); + ctx.setPacketHandled(msg.handle(inst, ctx)); + }); + } + + @Override + public PayloadHandlerFlow addMain(Type type, StreamCodec codec) { + return this.add(type, codec, (msg, ctx) -> { + ctx.enqueueWork(() -> { + var inst = ctx.getConnection().channel().attr(key).get(); + msg.handle(inst, ctx); + }); + ctx.setPacketHandled(true); + }); + } + + @Override + public PayloadHandlerFlow add(Type type, StreamCodec codec, BiConsumer consumer) { + ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer)); + return this; + } + + @Override + public PayloadHandlerFlow flow(@Nullable PacketFlow flow) { + return new HandlerFlow<>(key, ctx.flow(flow)); + } + } + + private interface ProtocolFactory extends PayloadConnection, ChannelBuildable { + BuilderContext ctx(); + + @Override + default PayloadProtocol protocol(NetworkProtocol protocol) { + return ctx().builder.protocol(protocol); + } + + @Override + default & CustomPacketPayload> PayloadHandlerProtocol protocol(AttributeKey context, NetworkProtocol protocol) { + return ctx().builder.protocol(context, protocol); + } + + @Override + default Channel build() { + return ctx().builder.build(); + } + } + } + + private record Message( + Type type, + @Nullable + NetworkProtocol protocol, + @Nullable + PacketFlow direction, + StreamCodec codec, + BiConsumer consumer + ){ }; + + private Message get(ResourceLocation type, boolean send) { + @SuppressWarnings("unchecked") + var msg = (Message)payloads.get(type); + + if (msg == null) + error((send ? "Attemped to send" : "Received") + " invalid message " + type + " on channel " + getName()); + + return msg; + } + + private void error(String message) { + LOGGER.error(MARKER, message); + throw new IllegalArgumentException(message); + } + + private void onPacketReceived(CustomPayloadEvent event) { + var data = event.getPayload(); + if (data == null) + error("Received empty payload on channel " + getName() + " login index " + event.getLoginIndex() + " for payload type " + event.getChannel()); + + var msg = get(event.getChannel(), false); + this.validate(event.getChannel(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false); + + var pkt = msg.codec().decode(data); + msg.consumer().accept(pkt, event.getSource()); + } + + @Override + protected Packet toVanillaPacket(Connection con, CustomPacketPayload message) { + var msg = get(message.type().id(), true); + this.validate(message.type().id(), con, msg.protocol(), msg.direction(), true); + return super.toVanillaPacket(con, message); + } + + @Override + ResourceLocation getName(CustomPacketPayload packet) { + return packet.type().id(); + } + + @Override + public void encode(FriendlyByteBuf out, CustomPacketPayload message) { + var msg = get(message.type().id(), true); + msg.codec().encode(out, message); + } +} diff --git a/src/main/java/net/minecraftforge/network/SimpleChannel.java b/src/main/java/net/minecraftforge/network/SimpleChannel.java index 28de4efd8e..bbadac77f9 100644 --- a/src/main/java/net/minecraftforge/network/SimpleChannel.java +++ b/src/main/java/net/minecraftforge/network/SimpleChannel.java @@ -12,7 +12,6 @@ import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap; import it.unimi.dsi.fastutil.objects.Object2ObjectMap; import net.minecraft.network.Connection; import net.minecraft.network.FriendlyByteBuf; -import net.minecraft.network.chat.Component; import net.minecraft.network.codec.StreamCodec; import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; @@ -505,6 +504,22 @@ public class SimpleChannel extends Channel implements SimpleConnection consumer ){ }; + private Message get(int id) { + @SuppressWarnings("unchecked") + var msg = (Message)byId.get(id); + if (msg == null) + error("Received invalid discriminator " + id + " on channel " + getName()); + return msg; + } + + private Message get(Object type) { + @SuppressWarnings("unchecked") + var msg = (Message)byType.get(type.getClass()); + if (msg == null) + error("Attemped to send invalid message " + type + " on channel " + getName()); + return msg; + } + protected int nextIndex() { while (byId.containsKey(lastIndex)) { lastIndex++; @@ -521,74 +536,29 @@ public class SimpleChannel extends Channel implements SimpleConnection)byId.get(id); - if (msg == null) { - LOGGER.error(MARKER, "Received invalid discriminator {} on channel {}", id, getName()); - return; - } - - var con = event.getSource().getConnection(); - var protocol = msg.protocol() == null ? con.getProtocol() : msg.protocol().toVanilla(); - var direction = msg.direction() == null ? con.getReceiving() : msg.direction(); - - if (protocol != con.getProtocol() || direction != con.getReceiving()) { - var error = "Illegal packet received, terminating connection. " + msg.type().getName() + " expected " + - direction.name() + " " + protocol.name() + " but was " + - con.getReceiving().name() + " " + con.getProtocol().name(); - - LOGGER.error(MARKER, error); - con.disconnect(Component.literal(error)); - throw new IllegalStateException(error); - } - - decodeAndDispatch(data, event.getSource(), msg); - } - - // Yay generics - private static void decodeAndDispatch(FriendlyByteBuf data, CustomPayloadEvent.Context ctx, Message msg) { - var message = msg.decoder().apply(data); - msg.consumer.accept(message, ctx); + var msg = get(data.readVarInt()); + super.validate(msg.type.getName(), event.getSource().getConnection(), msg.protocol(), msg.direction(), false); + var pkt = msg.decoder().apply(data); + msg.consumer().accept(pkt, event.getSource()); } @Override protected Packet toVanillaPacket(Connection con, Object message) { - var msg = byType.get(message.getClass()); - - if (msg == null) { - LOGGER.error(MARKER, "Attempted to send invalid message {} on channel {}", message.getClass().getName(), getName()); - throw new IllegalArgumentException("Invalid message " + message.getClass().getName()); - } - - var protocol = msg.protocol() == null ? con.getProtocol() : msg.protocol().toVanilla(); - var direction = msg.direction() == null ? con.getSending() : msg.direction(); - - if (protocol != con.getProtocol() || direction != con.getSending()) { - var error = "Illegal packet sent, terminating connection. " + msg.type().getName() + " expected " + - direction.name() + " " + protocol.name() + " but was " + - con.getSending().name() + " " + con.getProtocol().name(); - - LOGGER.error(MARKER, error); - con.disconnect(Component.literal(error)); - throw new IllegalStateException(error); - } - + var msg = get(message); + super.validate(message.getClass().getName(), con, msg.protocol(), msg.direction(), true); return super.toVanillaPacket(con, message); } @Override public void encode(FriendlyByteBuf out, Object message) { - @SuppressWarnings("unchecked") - var msg = (Message)byType.get(message.getClass()); - - if (msg == null) { - LOGGER.error(MARKER, "Attempted to send invalid message {} on channel {}", message.getClass().getName(), getName()); - throw new IllegalArgumentException("Invalid message " + message.getClass().getName()); - } - + var msg = get(message); out.writeVarInt(msg.index()); if (msg.encoder() != null) msg.encoder().accept(message, out); } + + private void error(String message) { + LOGGER.error(MARKER, message); + throw new IllegalArgumentException(message); + } } diff --git a/src/main/java/net/minecraftforge/network/payload/PayloadConnection.java b/src/main/java/net/minecraftforge/network/payload/PayloadConnection.java new file mode 100644 index 0000000000..5ef2f6cbaa --- /dev/null +++ b/src/main/java/net/minecraftforge/network/payload/PayloadConnection.java @@ -0,0 +1,172 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network.payload; + +import io.netty.util.AttributeKey; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraftforge.network.NetworkProtocol; +import net.minecraftforge.network.payload.handler.PayloadHandlerProtocol; +import net.minecraftforge.network.simple.handler.SimplePacket; + +import java.util.function.Consumer; + +import org.jetbrains.annotations.Nullable; + +public interface PayloadConnection { + /** + * Creates a builder grouping together all packets under the same protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + PayloadProtocol protocol(@Nullable NetworkProtocol protocol); + + /** + * Creates a builder grouping together all packets under the same protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + & CustomPacketPayload> PayloadHandlerProtocol protocol(AttributeKey context, @Nullable NetworkProtocol protocol); + + /** + * Consumer version of {@link #protocol(NetworkProtocol)}. The Consumer will immediately be called with the created protocol. + */ + default PayloadConnection protocol(@Nullable NetworkProtocol protocol, Consumer> consumer) { + var tmp = this.protocol(protocol); + consumer.accept(tmp); + return this; + } + + /** + * Consumer version of {@link #protocol(AttributeKey,NetworkProtocol)}. The Consumer will immediately be called with the created protocol. + */ + default & CustomPacketPayload> PayloadConnection protocol(AttributeKey context, @Nullable NetworkProtocol protocol, Consumer> consumer) { + var tmp = this.protocol(context, protocol); + consumer.accept(tmp); + return this; + } + + /** + * Creates a builder grouping together packets that are only valid when under the Configuration protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default PayloadProtocol configuration() { + return protocol(NetworkProtocol.CONFIGURATION); + } + + /** + * Creates a builder grouping together packets that are only valid when under the Configuration protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default & CustomPacketPayload> PayloadHandlerProtocol configuration(AttributeKey context) { + return protocol(context, NetworkProtocol.CONFIGURATION); + } + + /** + * Consumer version of {@link #configuration()}. The Consumer will immediately be called with the created protocol. + */ + default PayloadConnection configuration(Consumer> consumer) { + return protocol(NetworkProtocol.CONFIGURATION, consumer); + } + + /** + * Consumer version of {@link #configuration(AttributeKey)}. The Consumer will immediately be called with the created protocol. + */ + default & CustomPacketPayload> PayloadConnection configuration(AttributeKey context, Consumer> consumer) { + return protocol(context, NetworkProtocol.CONFIGURATION, consumer); + } + + /** + * Creates a builder grouping together packets that are only valid when under the Login protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default PayloadProtocol login() { + return protocol(NetworkProtocol.LOGIN); + } + + /** + * Creates a builder grouping together packets that are only valid when under the Login protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default & CustomPacketPayload> PayloadHandlerProtocol login(AttributeKey context) { + return protocol(context, NetworkProtocol.LOGIN); + } + + /** + * Consumer version of {@link #login()}. The Consumer will immediately be called with the created protocol. + */ + default PayloadConnection login(Consumer> consumer) { + return protocol(NetworkProtocol.LOGIN, consumer); + } + + /** + * Consumer version of {@link #login(AttributeKey)}. The Consumer will immediately be called with the created protocol. + */ + default & CustomPacketPayload> PayloadConnection login(AttributeKey context, Consumer> consumer) { + return protocol(context, NetworkProtocol.LOGIN, consumer); + } + + /** + * Creates a builder grouping together packets that are only valid when under the Play protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default PayloadProtocol play() { + return protocol(NetworkProtocol.PLAY); + } + + /** + * Creates a builder grouping together packets that are only valid when under the Login protocol. + * This will validate that the protocol matches before the packet is sent or received. + */ + default & CustomPacketPayload> PayloadHandlerProtocol play(AttributeKey context) { + return protocol(context, NetworkProtocol.PLAY); + } + + /** + * Consumer version of {@link #play()}. The Consumer will immediately be called with the created protocol. + */ + default PayloadConnection play(Consumer> consumer) { + return protocol(NetworkProtocol.PLAY, consumer); + } + + /** + * Consumer version of {@link #play(AttributeKey)}. The Consumer will immediately be called with the created protocol. + */ + default & CustomPacketPayload> PayloadConnection play(AttributeKey context, Consumer> consumer) { + return protocol(context, NetworkProtocol.PLAY, consumer); + } + + /** + * Creates a builder grouping together packets that are valid under any protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default PayloadProtocol any() { + return protocol(null); + } + + /** + * Creates a builder grouping together packets that are valid under any protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default & CustomPacketPayload> PayloadHandlerProtocol any(AttributeKey context) { + return protocol(context, null); + } + + /** + * Consumer version of {@link #any()}. The Consumer will immediately be called with the created protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default PayloadConnection any(Consumer> consumer) { + return protocol(null, consumer); + } + + /** + * Consumer version of {@link #any(AttributeKey)}. The Consumer will immediately be called with the created protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default & CustomPacketPayload> PayloadConnection any(AttributeKey context, Consumer> consumer) { + return protocol(context, null, consumer); + } +} diff --git a/src/main/java/net/minecraftforge/network/payload/PayloadFlow.java b/src/main/java/net/minecraftforge/network/payload/PayloadFlow.java new file mode 100644 index 0000000000..785722ff3e --- /dev/null +++ b/src/main/java/net/minecraftforge/network/payload/PayloadFlow.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network.payload; + +import java.util.function.BiConsumer; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type; +import net.minecraftforge.event.network.CustomPayloadEvent; +import net.minecraftforge.event.network.CustomPayloadEvent.Context; +import net.minecraftforge.network.ChannelBuildable; + +public interface PayloadFlow extends PayloadProtocol, ChannelBuildable { + /** + * Adds a packet to this channel that has it's protocol validated whenever sent or received. + *

+ * The handler is called on the network thread, and so should not interact with most game state by default. + * {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or + * client thread. + */ + PayloadFlow add(Type type, StreamCodec codec, BiConsumer handler); + + /** + * Adds a packet to this channel that has it's protocol validated whenever sent or received. + *

+ * Unlike {@link #add(Class,StreamCodec,BiConsumer)}, the consumer is called on the main thread, and so can + * interact with most game state by default. + */ + default PayloadFlow addMain(Type type, StreamCodec codec, BiConsumer handler) { + return add(type, codec, (msg, ctx) -> { + ctx.enqueueWork(() -> handler.accept(msg, ctx)); + ctx.setPacketHandled(true); + }); + } +} diff --git a/src/main/java/net/minecraftforge/network/payload/PayloadProtocol.java b/src/main/java/net/minecraftforge/network/payload/PayloadProtocol.java new file mode 100644 index 0000000000..fd2412331c --- /dev/null +++ b/src/main/java/net/minecraftforge/network/payload/PayloadProtocol.java @@ -0,0 +1,16 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network.payload; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraftforge.network.simple.BaseProtocol; + +/** + * Entry point for a {@link net.minecraftforge.network.PayloadChannel PayloadChannel} protocol without any extra context handler marshaling. + */ +public interface PayloadProtocol extends BaseProtocol, PayloadProtocol>, PayloadConnection { +} diff --git a/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerFlow.java b/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerFlow.java new file mode 100644 index 0000000000..7a3e6a95fc --- /dev/null +++ b/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerFlow.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network.payload.handler; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.codec.StreamCodec; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload.Type; +import net.minecraftforge.event.network.CustomPayloadEvent; +import net.minecraftforge.network.ChannelBuildable; + +import java.util.function.BiConsumer; + +public interface PayloadHandlerFlow extends PayloadHandlerProtocol, ChannelBuildable { + /** + * Adds a packet to this channel that has it's protocol validated whenever sent or received. + *

+ * The handler is called on the network thread, and so should not interact with most game state by default. + * {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or + * client thread. Alternatively one can use {@link #addMain(Class, StreamCodec)} to run the handler on the + * main thread. + */ + PayloadHandlerFlow add(Type type, StreamCodec codec); + + /** + * Adds a packet to this channel that has it's protocol validated whenever sent or received. + *

+ * Unlike {@link #add(Type,StreamCodec)}, the consumer is called on the main thread, and so can + * interact with most game state by default. + */ + PayloadHandlerFlow addMain(Type type, StreamCodec codec); + + /** + * Adds a packet to this channel that has it's protocol validated whenever sent or received. + *

+ * The handler is called on the network thread, and so should not interact with most game state by default. + * {@link CustomPayloadEvent.Context#enqueueWork(Runnable)} can be used to handle the message on the main server or + * client thread. + */ + PayloadHandlerFlow add(Type type, StreamCodec codec, BiConsumer handler); +} diff --git a/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerProtocol.java b/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerProtocol.java new file mode 100644 index 0000000000..4282434d47 --- /dev/null +++ b/src/main/java/net/minecraftforge/network/payload/handler/PayloadHandlerProtocol.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) Forge Development LLC and contributors + * SPDX-License-Identifier: LGPL-2.1-only + */ + +package net.minecraftforge.network.payload.handler; + +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.protocol.common.custom.CustomPacketPayload; +import net.minecraftforge.network.payload.PayloadConnection; +import net.minecraftforge.network.simple.BaseProtocol; + +/** + * Entry point for a {@link net.minecraftforge.network.PayloadChannel PayloadChannel} protocol that mimics vanilla's + * {@link net.minecraft.network.protocol.Packet Packet} system where every packet must implement + * {@link SimplePacket} and will have their {@link SimplePacket#handle(Object, net.minecraftforge.event.network.CustomPayloadEvent.Context) handle(CTX, Context)} + * method called. + */ +public interface PayloadHandlerProtocol extends BaseProtocol, PayloadHandlerProtocol>, PayloadConnection { +} diff --git a/src/main/java/net/minecraftforge/network/simple/SimpleBuildable.java b/src/main/java/net/minecraftforge/network/simple/SimpleBuildable.java index 736618633d..8f3081164b 100644 --- a/src/main/java/net/minecraftforge/network/simple/SimpleBuildable.java +++ b/src/main/java/net/minecraftforge/network/simple/SimpleBuildable.java @@ -5,12 +5,14 @@ package net.minecraftforge.network.simple; +import net.minecraftforge.network.ChannelBuildable; import net.minecraftforge.network.SimpleChannel; -public interface SimpleBuildable { +public interface SimpleBuildable extends ChannelBuildable { /* * This will build the entire channel, locking any future modifications from happening. * This should be the LAST call in your builder chain and can only be called once. */ + @Override SimpleChannel build(); } diff --git a/src/main/java/net/minecraftforge/network/simple/SimpleConnection.java b/src/main/java/net/minecraftforge/network/simple/SimpleConnection.java index a13ab98a78..540efed364 100644 --- a/src/main/java/net/minecraftforge/network/simple/SimpleConnection.java +++ b/src/main/java/net/minecraftforge/network/simple/SimpleConnection.java @@ -135,4 +135,36 @@ public interface SimpleConnection { default > SimpleConnection play(AttributeKey context, Consumer> consumer) { return protocol(context, NetworkProtocol.PLAY, consumer); } + + /** + * Creates a builder grouping together packets that are valid under any protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default SimpleProtocol any() { + return protocol(null); + } + + /** + * Creates a builder grouping together packets that are valid under any protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default > SimpleHandlerProtocol any(AttributeKey context) { + return protocol(context, null); + } + + /** + * Consumer version of {@link #any()}. The Consumer will immediately be called with the created protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default SimpleConnection any(Consumer> consumer) { + return protocol(null, consumer); + } + + /** + * Consumer version of {@link #any(AttributeKey)}. The Consumer will immediately be called with the created protocol. + * It is not recommended to do this, instead you should use one of the other methods in this class to make sure your packets have basic validation. + */ + default > SimpleConnection any(AttributeKey context, Consumer> consumer) { + return protocol(context, null, consumer); + } }