Remove ICustomPacket and add PayloadChannel. (#9972)

New PayloadChannel that uses the vanilla payload Type for packet distinction
Implement the minecraft:register/unregister channels using the new PayloadChannel
New generic channel builder function allowing people to implement channels however they want.
This commit is contained in:
LexManos authored and GitHub committed 2024-05-14 08:36:42 -07:00
1 parent 640c206117
commit 2b3a4137e1
34 files changed
+1035 -338

No files matched your search

@@ -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 <B extends FriendlyByteBuf> StreamCodec<B, ? extends CustomPacketPayload> getCustomPayloadCodec(ResourceLocation id, int max) {
var channel = NetworkRegistry.findTarget(id);
if (channel == null)
return DiscardedPayload.codec(id, max);
return StreamCodec.<B, ForgePayload>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<ClientGamePacketListener> getEntitySpawnPacket(Entity entity) {
if (!(entity instanceof IEntityAdditionalSpawnData add))
throw new IllegalArgumentException(entity.getClass() + " is not an instance of " + IEntityAdditionalSpawnData.class);
return (Packet<ClientGamePacketListener>)NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity));
return NetworkDirection.PLAY_TO_CLIENT.buildPacket(NetworkInitialization.PLAY, new SpawnEntity(entity));
}
@ApiStatus.Internal
@@ -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 <T extends PacketListener> String hex(ProtocolInfo protocol, Packet packet) {
var buf = Unpooled.buffer();
protocol.codec().encode(buf, packet);
return HexDumper.dump(buf);
}
}
@@ -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() {
@@ -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<MSG> {
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<MSG> {
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<MSG> {
* 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<MSG> {
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<MSG> {
* @param <MSG> 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;
@@ -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<T> {
/*
* 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<T> build();
}
@@ -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<AttributeKey<?>, Function<Connection, ?>> attributes = new HashMap<>();
private Consumer<Connection> 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&lt;CustomPacketPayload&gt}
*/
public PayloadConnection<CustomPacketPayload> 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> C channel(Function<NetworkInstance, C> factory) {
return factory.apply(createNetworkInstance());
}
}
@@ -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<CustomPacketPayload> 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<String> channels) implements CustomPacketPayload {
private static Type<Register> TYPE = CustomPacketPayload.createType("minecraft:register");
private static StreamCodec<FriendlyByteBuf, Register> CODEC = StreamCodec.of(
(buf, v) -> encode(buf, v.channels),
buf -> new Register(decode(buf))
);
@Override
public Type<Register> type() {
return TYPE;
}
}
private record Unregister(List<String> channels) implements CustomPacketPayload {
private static Type<Unregister> TYPE = CustomPacketPayload.createType("minecraft:unregister");
private static StreamCodec<FriendlyByteBuf, Unregister> CODEC = StreamCodec.of(
(buf, v) -> encode(buf, v.channels),
buf -> new Unregister(decode(buf))
);
@Override
public Type<Unregister> 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<ResourceLocation> channels) {
var list = NetworkContext.get(connection);
var toSend = new HashSet<ResourceLocation>();
var toSend = new HashSet<String>();
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<ResourceLocation> channels) {
var list = NetworkContext.get(connection);
var toSend = new HashSet<ResourceLocation>();
var toSend = new HashSet<String>();
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<ResourceLocation> channels) {
if (channels.isEmpty())
return;
var buf = new FriendlyByteBuf(Unpooled.buffer());
private static void encode(FriendlyByteBuf buf, List<String> 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<String> decode(FriendlyByteBuf buffer) {
byte[] data = new byte[Math.max(buffer.readableBytes(), 0)];
buffer.readBytes(data);
var channels = new HashSet<String>();
var channels = new ArrayList<String>();
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<String> channels, final ChannelRegistrationChangeEvent.Type changeType) {
var changed = new HashSet<ResourceLocation>();
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);
}
}
}
@@ -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) {
@@ -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<FriendlyByteBuf> 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<FriendlyByteBuf> encoder) {
return new ForgePayload(id, null, encoder);
}
@Override
public void write(FriendlyByteBuf buf) {
encoder.accept(buf);
}
@Override
public Type<? extends CustomPacketPayload> type() {
return new Type<>(this.id());
}
}
@@ -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<T extends Packet<?>> {
/**
* 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;
}
}
@@ -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<B extends FriendlyByteBuf>(NetworkProtocol<B> protocol, PacketFlow direction) {
@@ -18,7 +19,7 @@ public record NetworkDirection<B extends FriendlyByteBuf>(NetworkProtocol<B> pro
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_CLIENT = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.CLIENTBOUND);
public static final NetworkDirection<FriendlyByteBuf> CONFIGURATION_TO_SERVER = new NetworkDirection<>(NetworkProtocol.CONFIGURATION, PacketFlow.SERVERBOUND);
public <T extends Packet<?>, MSG> ICustomPacket<T> buildPacket(Channel<MSG> channel, MSG packet) {
public <T extends PacketListener, MSG> Packet<T> buildPacket(Channel<MSG> channel, MSG packet) {
return this.protocol().buildPacket(this.direction(), channel, packet);
}
}
@@ -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<ForgePacketHandler> 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());
}
@@ -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<AttributeKey<?>, Function<Connection, ?>> attributes;
final Consumer<Connection> channelHandler;
final ServerStatusPing.ChannelData pingData;
private final Set<ResourceLocation> 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);
}
}
@@ -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<B extends FriendlyByteBuf> {
public static final NetworkProtocol<RegistryFriendlyByteBuf> PLAY = new NetworkProtocol<>(ConnectionProtocol.PLAY);
@@ -41,23 +40,26 @@ public class NetworkProtocol<B extends FriendlyByteBuf> {
return this.protocol;
}
public <T extends PacketListener, MSG> Packet<T> buildPacket(PacketFlow direction, Channel<MSG> channel, MSG packet) {
return buildPacket(direction, channel.getName(packet), buf -> channel.encode(buf, packet));
}
@SuppressWarnings("unchecked")
public <T extends Packet<?>, MSG> ICustomPacket<T> buildPacket(PacketFlow direction, Channel<MSG> channel, MSG packet) {
var name = channel.getName();
var encoder = (Consumer<FriendlyByteBuf>)(buf -> channel.encode(buf, packet));
public <T extends PacketListener> Packet<T> buildPacket(PacketFlow direction, ResourceLocation name, Consumer<B> encoder) {
var payload = ForgePayload.create(name, (Consumer<FriendlyByteBuf>)encoder);
switch (this.protocol) {
case PLAY, CONFIGURATION:
if (direction == PacketFlow.CLIENTBOUND)
return (ICustomPacket<T>)new ClientboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder));
return (Packet<T>)new ClientboundCustomPayloadPacket(payload);
else
return (ICustomPacket<T>)new ServerboundCustomPayloadPacket(new DiscardedPayload(name, null, encoder));
return (Packet<T>)new ServerboundCustomPayloadPacket(payload);
case LOGIN:
if (direction == PacketFlow.CLIENTBOUND)
return (ICustomPacket<T>)new ClientboundCustomQueryPacket(0, new DiscardedQueryPayload(name, null, encoder));
return (Packet<T>)new ClientboundCustomQueryPacket(0, payload);
else
return (ICustomPacket<T>)new ServerboundCustomQueryAnswerPacket(0, new DiscardedQueryAnswerPayload(null, encoder));
return (Packet<T>)new ServerboundCustomQueryAnswerPacket(0, payload);
default:
throw new IllegalArgumentException("Invalid protocol, shouldn't be possible as this is a private class..");
@@ -41,25 +41,28 @@ public class NetworkRegistry {
static final Logger LOGGER = LogManager.getLogger();
static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY");
static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
private static Map<ResourceLocation, NetworkInstance> instances = Collections.synchronizedMap(new HashMap<>());
private static Map<ResourceLocation, NetworkInstance> 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<ResourceLocation, ServerStatusPing.ChannelData> 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<ResourceLocation, ServerStatusPing.ChannelData>();
for (var channel : instances.values()) {
ret.put(channel.getChannelName(), channel.pingData);
}
return ret;
}
static List<String> listRejectedVanillaMods(Function<NetworkInstance, VersionTest> testFunction) {
@@ -86,7 +89,8 @@ public class NetworkRegistry {
Set<ResourceLocation> missing = new HashSet<>();
Map<ResourceLocation, NetworkMismatchData.Version> 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<ResourceLocation, Integer> buildChannelVersions() {
var ret = new Object2IntOpenHashMap<ResourceLocation>(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<ResourceLocation> buildRegisterList() {
var ret = new ArrayList<ResourceLocation>(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);
}
}
@@ -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<CustomPacketPayload> {
private static final Logger LOGGER = LogManager.getLogger();
private static final Marker MARKER = MarkerManager.getMarker("PAYLOAD_CHANNEL");
private final Map<ResourceLocation, Message<?, ?>> payloads;
@ApiStatus.Internal
private PayloadChannel(Builder builder) {
super(builder.instance);
builder.instance.addListener(this::onPacketReceived);
this.payloads = Collections.unmodifiableMap(builder.payloads);
}
static PayloadConnection<CustomPacketPayload> builder(NetworkInstance instance) {
return new Builder(instance);
}
private final static class Builder implements PayloadConnection<CustomPacketPayload> {
private final NetworkInstance instance;
private boolean built = false;
private final Map<ResourceLocation, Message<?, ?>> payloads = new HashMap<>();
private Builder(NetworkInstance instance) {
this.instance = instance;
}
@Override
public <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
return new Protocol<>(new BuilderContext<NEWBUF, NEWBASE>(this).protocol(protocol));
}
@Override
public <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends CustomPacketPayload & SimplePacket<CTX>> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
return new HandlerProtocol<>(context, new BuilderContext<NEWBUF, NEWBASE>(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<BUF extends FriendlyByteBuf, BASE>(Builder builder, @Nullable NetworkProtocol<BUF> protocol, @Nullable PacketFlow flow) {
public BuilderContext(Builder builder) {
this(builder, null, null);
}
public <NEWBUF extends FriendlyByteBuf> BuilderContext<NEWBUF, BASE> protocol(NetworkProtocol<NEWBUF> protocol) {
return new BuilderContext<>(builder, protocol, flow);
}
private BuilderContext<BUF, BASE> flow(@Nullable PacketFlow flow) {
return new BuilderContext<>(builder, protocol, flow);
}
}
private record Protocol<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadProtocol<BUF, BASE> {
@Override
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
return new Flow<>(ctx.flow(flow));
}
}
private record HandlerProtocol<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
AttributeKey<CTX> key,
BuilderContext<BUF, BASE> ctx
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerProtocol<BUF, BASE> {
@Override
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
return new HandlerFlow<>(key, ctx.flow(flow));
}
}
private record Flow<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload>(BuilderContext<BUF, BASE> ctx) implements ProtocolFactory<BUF, BASE>, PayloadFlow<BUF, BASE> {
@Override
public <MSG extends BASE> Flow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
return this;
}
@Override
public PayloadFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
return new Flow<>(ctx.flow(flow));
}
}
private record HandlerFlow<BUF extends FriendlyByteBuf, CTX, BASE extends CustomPacketPayload & SimplePacket<CTX>>(
AttributeKey<CTX> key,
BuilderContext<BUF, BASE> ctx
) implements ProtocolFactory<BUF, BASE>, PayloadHandlerFlow<BUF, BASE> {
@Override
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec) {
return this.add(type, codec, (msg, ctx) -> {
var inst = ctx.getConnection().channel().attr(key).get();
ctx.setPacketHandled(msg.handle(inst, ctx));
});
}
@Override
public <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> 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 <MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> consumer) {
ctx.builder().add(new Message<>(type, ctx.protocol(), ctx.flow(), codec, consumer));
return this;
}
@Override
public PayloadHandlerFlow<BUF, BASE> flow(@Nullable PacketFlow flow) {
return new HandlerFlow<>(key, ctx.flow(flow));
}
}
private interface ProtocolFactory<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadConnection<BASE>, ChannelBuildable<CustomPacketPayload> {
BuilderContext<BUF, BASE> ctx();
@Override
default <NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(NetworkProtocol<NEWBUF> protocol) {
return ctx().builder.protocol(protocol);
}
@Override
default <NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, NetworkProtocol<NEWBUF> protocol) {
return ctx().builder.protocol(context, protocol);
}
@Override
default Channel<CustomPacketPayload> build() {
return ctx().builder.build();
}
}
}
private record Message<MSG extends CustomPacketPayload, BUF extends FriendlyByteBuf>(
Type<MSG> type,
@Nullable
NetworkProtocol<BUF> protocol,
@Nullable
PacketFlow direction,
StreamCodec<BUF, MSG> codec,
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
){ };
private Message<CustomPacketPayload, FriendlyByteBuf> get(ResourceLocation type, boolean send) {
@SuppressWarnings("unchecked")
var msg = (Message<CustomPacketPayload, FriendlyByteBuf>)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);
}
}
@@ -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<Object> implements SimpleConnection<O
BiConsumer<MSG, CustomPayloadEvent.Context> consumer
){ };
private Message<Object, FriendlyByteBuf> get(int id) {
@SuppressWarnings("unchecked")
var msg = (Message<Object, FriendlyByteBuf>)byId.get(id);
if (msg == null)
error("Received invalid discriminator " + id + " on channel " + getName());
return msg;
}
private Message<Object, FriendlyByteBuf> get(Object type) {
@SuppressWarnings("unchecked")
var msg = (Message<Object, FriendlyByteBuf>)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<Object> implements SimpleConnection<O
return;
}
int id = data.readVarInt();
@SuppressWarnings("unchecked")
var msg = (Message<?, FriendlyByteBuf>)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 <MSG> void decodeAndDispatch(FriendlyByteBuf data, CustomPayloadEvent.Context ctx, Message<MSG, FriendlyByteBuf> 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<Object, FriendlyByteBuf>)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);
}
}
@@ -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<BASE extends CustomPacketPayload> {
/**
* 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.
*/
<NEWBUF extends FriendlyByteBuf, NEWBASE extends CustomPacketPayload> PayloadProtocol<NEWBUF, NEWBASE> protocol(@Nullable NetworkProtocol<NEWBUF> 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.
*/
<NEWBUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<NEWBUF, NEWBASE> protocol(AttributeKey<CTX> context, @Nullable NetworkProtocol<NEWBUF> protocol);
/**
* Consumer version of {@link #protocol(NetworkProtocol)}. The Consumer will immediately be called with the created protocol.
*/
default <BUF extends FriendlyByteBuf> PayloadConnection<BASE> protocol(@Nullable NetworkProtocol<BUF> protocol, Consumer<PayloadProtocol<BUF, BASE>> consumer) {
var tmp = this.<BUF, BASE>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 <BUF extends FriendlyByteBuf, CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> protocol(AttributeKey<CTX> context, @Nullable NetworkProtocol<BUF> protocol, Consumer<PayloadHandlerProtocol<BUF, NEWBASE>> consumer) {
var tmp = this.<BUF, CTX, NEWBASE>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<FriendlyByteBuf, BASE> 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 <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> configuration(AttributeKey<CTX> context) {
return protocol(context, NetworkProtocol.CONFIGURATION);
}
/**
* Consumer version of {@link #configuration()}. The Consumer will immediately be called with the created protocol.
*/
default PayloadConnection<BASE> configuration(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> consumer) {
return protocol(NetworkProtocol.CONFIGURATION, consumer);
}
/**
* Consumer version of {@link #configuration(AttributeKey)}. The Consumer will immediately be called with the created protocol.
*/
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> configuration(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> 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<FriendlyByteBuf, BASE> 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 <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> login(AttributeKey<CTX> context) {
return protocol(context, NetworkProtocol.LOGIN);
}
/**
* Consumer version of {@link #login()}. The Consumer will immediately be called with the created protocol.
*/
default PayloadConnection<BASE> login(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> consumer) {
return protocol(NetworkProtocol.LOGIN, consumer);
}
/**
* Consumer version of {@link #login(AttributeKey)}. The Consumer will immediately be called with the created protocol.
*/
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> login(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> 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<RegistryFriendlyByteBuf, BASE> 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 <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE> play(AttributeKey<CTX> context) {
return protocol(context, NetworkProtocol.PLAY);
}
/**
* Consumer version of {@link #play()}. The Consumer will immediately be called with the created protocol.
*/
default PayloadConnection<BASE> play(Consumer<PayloadProtocol<RegistryFriendlyByteBuf, BASE>> consumer) {
return protocol(NetworkProtocol.PLAY, consumer);
}
/**
* Consumer version of {@link #play(AttributeKey)}. The Consumer will immediately be called with the created protocol.
*/
default <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> play(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE>> 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<FriendlyByteBuf, BASE> 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 <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE> any(AttributeKey<CTX> 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<BASE> any(Consumer<PayloadProtocol<FriendlyByteBuf, BASE>> 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 <CTX, NEWBASE extends SimplePacket<CTX> & CustomPacketPayload> PayloadConnection<BASE> any(AttributeKey<CTX> context, Consumer<PayloadHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
return protocol(context, null, consumer);
}
}
@@ -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<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadProtocol<BUF, BASE>, ChannelBuildable<CustomPacketPayload> {
/**
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
* <p>
* 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.
*/
<MSG extends BASE> PayloadFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler);
/**
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
* <p>
* 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 <MSG extends BASE> PayloadFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, Context> handler) {
return add(type, codec, (msg, ctx) -> {
ctx.enqueueWork(() -> handler.accept(msg, ctx));
ctx.setPacketHandled(true);
});
}
}
@@ -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<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends BaseProtocol<PayloadFlow<BUF, BASE>, PayloadProtocol<BUF, BASE>>, PayloadConnection<BASE> {
}
@@ -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<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends PayloadHandlerProtocol<BUF, BASE>, ChannelBuildable<CustomPacketPayload> {
/**
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
* <p>
* 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.
*/
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec);
/**
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
* <p>
* Unlike {@link #add(Type,StreamCodec)}, the consumer is called on the main thread, and so can
* interact with most game state by default.
*/
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> addMain(Type<MSG> type, StreamCodec<BUF, MSG> codec);
/**
* Adds a packet to this channel that has it's protocol validated whenever sent or received.
* <p>
* 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.
*/
<MSG extends BASE> PayloadHandlerFlow<BUF, BASE> add(Type<MSG> type, StreamCodec<BUF, MSG> codec, BiConsumer<MSG, CustomPayloadEvent.Context> handler);
}
@@ -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<BUF extends FriendlyByteBuf, BASE extends CustomPacketPayload> extends BaseProtocol<PayloadHandlerFlow<BUF, BASE>, PayloadHandlerProtocol<BUF, BASE>>, PayloadConnection<BASE> {
}
@@ -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<Object> {
/*
* 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();
}
@@ -135,4 +135,36 @@ public interface SimpleConnection<BASE> {
default <CTX, NEWBASE extends SimplePacket<CTX>> SimpleConnection<BASE> play(AttributeKey<CTX> context, Consumer<SimpleHandlerProtocol<RegistryFriendlyByteBuf, NEWBASE>> 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<FriendlyByteBuf, BASE> 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 <CTX, NEWBASE extends SimplePacket<CTX>> SimpleHandlerProtocol<FriendlyByteBuf, NEWBASE> any(AttributeKey<CTX> 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<BASE> any(Consumer<SimpleProtocol<FriendlyByteBuf, BASE>> 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 <CTX, NEWBASE extends SimplePacket<CTX>> SimpleConnection<BASE> any(AttributeKey<CTX> context, Consumer<SimpleHandlerProtocol<FriendlyByteBuf, NEWBASE>> consumer) {
return protocol(context, null, consumer);
}
}