From abacf8e141e654d392c63dc064c7f91d22556a00 Mon Sep 17 00:00:00 2001 From: cpw Date: Sun, 16 Sep 2018 20:54:03 -0400 Subject: [PATCH] Net handling pass 2. Tidied up login packet sourcing, generate registry packets. --- build.gradle | 23 +- .../net/minecraft/item/Item.java.patch | 2 +- .../net/minecraftforge/fml/LaunchTesting.java | 4 +- .../fml/network/FMLHandshakeHandler.java | 240 ++++++++++++++++++ ...Message.java => FMLHandshakeMessages.java} | 64 +++-- .../fml/network/FMLLoginWrapper.java | 65 +++++ .../fml/network/FMLNetworking.java | 138 +--------- .../fml/network/ICustomPacket.java | 2 +- .../fml/network/NetworkEvent.java | 56 +++- .../fml/network/NetworkHooks.java | 11 +- .../fml/network/NetworkInstance.java | 18 +- .../fml/network/NetworkRegistry.java | 234 ++++++++++++++--- .../fml/network/PacketDispatcher.java | 48 ++++ .../network/simple/IndexedMessageCodec.java | 14 + .../fml/network/simple/SimpleChannel.java | 44 +++- .../fml/util/ThreeConsumer.java | 28 ++ .../registries/RegistryManager.java | 16 ++ 17 files changed, 787 insertions(+), 220 deletions(-) create mode 100644 src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java rename src/main/java/net/minecraftforge/fml/network/{FMLHandshakeMessage.java => FMLHandshakeMessages.java} (57%) create mode 100644 src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java create mode 100644 src/main/java/net/minecraftforge/fml/network/PacketDispatcher.java create mode 100644 src/main/java/net/minecraftforge/fml/util/ThreeConsumer.java diff --git a/build.gradle b/build.gradle index 00f06c131..07a859483 100644 --- a/build.gradle +++ b/build.gradle @@ -49,6 +49,7 @@ project(':clean') { project(':forge') { evaluationDependsOn(':clean') + apply plugin: 'java-library' apply plugin: 'eclipse' apply plugin: 'net.minecraftforge.gradle.forgedev.patcher' sourceSets { @@ -95,17 +96,17 @@ project(':forge') { maxFuzz 3 } dependencies { - implementation 'net.minecraft:client:1.13:extra' - implementation 'cpw.mods:modlauncher:0.1.0-rc.3' //Pinned until cpw fixes getCommonSupertype - implementation 'net.minecraftforge:accesstransformers:0.10+:shadowed' - implementation 'net.minecraftforge:eventbus:0.1+:service' - implementation 'net.minecraftforge:forgespi:0.1+' - implementation 'net.minecraftforge:coremods:0.1+' - implementation 'com.electronwill.night-config:core:3.4.0' - implementation 'com.electronwill.night-config:toml:3.4.0' - implementation 'org.jline:jline:3.5.1' - implementation 'org.apache.maven:maven-artifact:3.5.3' - implementation 'java3d:vecmath:1.5.2' + api 'net.minecraft:client:1.13:extra' + api 'cpw.mods:modlauncher:0.1.0-rc.3' //Pinned until cpw fixes getCommonSupertype + api 'net.minecraftforge:accesstransformers:0.10+:shadowed' + api 'net.minecraftforge:eventbus:0.1+:service' + api 'net.minecraftforge:forgespi:0.1+' + api 'net.minecraftforge:coremods:0.1+' + api 'com.electronwill.night-config:core:3.4.0' + api 'com.electronwill.night-config:toml:3.4.0' + api 'org.jline:jline:3.5.1' + api 'org.apache.maven:maven-artifact:3.5.3' + api 'java3d:vecmath:1.5.2' } configurations { diff --git a/patches/minecraft/net/minecraft/item/Item.java.patch b/patches/minecraft/net/minecraft/item/Item.java.patch index 775a1258c..736b9ea1e 100644 --- a/patches/minecraft/net/minecraft/item/Item.java.patch +++ b/patches/minecraft/net/minecraft/item/Item.java.patch @@ -60,7 +60,7 @@ float f7 = f2 * f4; - double d3 = 5.0D; - Vec3d vec3d1 = vec3d.add((double)f6 * 5.0D, (double)f5 * 5.0D, (double)f7 * 5.0D); -+ double d3 = 6; ++ double d3 = playerIn.getEntityAttribute(EntityPlayer.REACH_DISTANCE).getAttributeValue(); + Vec3d vec3d1 = vec3d.add((double)f6 * d3, (double)f5 * d3, (double)f7 * d3); return worldIn.func_200259_a(vec3d, vec3d1, useLiquids ? RayTraceFluidMode.SOURCE_ONLY : RayTraceFluidMode.NEVER, false, false); } diff --git a/src/main/java/net/minecraftforge/fml/LaunchTesting.java b/src/main/java/net/minecraftforge/fml/LaunchTesting.java index 9435c6307..44e4b94b2 100644 --- a/src/main/java/net/minecraftforge/fml/LaunchTesting.java +++ b/src/main/java/net/minecraftforge/fml/LaunchTesting.java @@ -45,9 +45,9 @@ public class LaunchTesting logcontext.getConfiguration().addFilter(axformFilter); logcontext.getConfiguration().addFilter(eventbusFilter); logcontext.updateLoggers(); - File invsorter = new File("/home/cpw/projects/mods/inventorysorter/classes"); + File invsorter = new File("/home/cpw/projects/minecraft/inventorysorter/classes"); if (invsorter.exists()) { - System.setProperty("fml.explodedDir", "/home/cpw/projects/mods/inventorysorter/classes"); //TODO: Move this to a example included in our tests, not a random location... + System.setProperty("fml.explodedDir", "/home/cpw/projects/minecraft/inventorysorter/classes"); //TODO: Move this to a example included in our tests, not a random location... } String assets = System.getenv().getOrDefault("assetDirectory", "assets"); String target = System.getenv().get("target"); diff --git a/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java b/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java new file mode 100644 index 000000000..07015316c --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/network/FMLHandshakeHandler.java @@ -0,0 +1,240 @@ +package net.minecraftforge.fml.network; + +import io.netty.util.AttributeKey; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.PacketBuffer; +import net.minecraft.server.network.NetHandlerLoginServer; +import net.minecraft.util.ResourceLocation; +import net.minecraft.util.text.TextComponentString; +import net.minecraftforge.fml.Logging; +import net.minecraftforge.fml.network.simple.SimpleChannel; +import net.minecraftforge.fml.util.ThreeConsumer; +import net.minecraftforge.registries.RegistryManager; +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 java.util.ArrayList; +import java.util.List; +import java.util.function.BiConsumer; +import java.util.function.Supplier; + +/** + * Instance responsible for handling the overall FML network handshake. + * + *

An instance is created during {@link net.minecraft.network.handshake.client.CPacketHandshake} handling, and attached + * to the {@link NetworkManager#channel} via {@link #FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY}. + * + *

The {@link #channel} is a {@link SimpleChannel} with standard messages flowing in both directions. + * + *

The {@link #loginWrapper} transforms these messages into {@link net.minecraft.network.login.client.CPacketCustomPayloadLogin} + * and {@link net.minecraft.network.login.server.SPacketCustomPayloadLogin} compatible messages, by means of wrapping. + * + *

The handshake is ticked {@link #tickLogin(NetworkManager)} from the {@link NetHandlerLoginServer#update()} method, + * utilizing the {@link NetHandlerLoginServer.LoginState#NEGOTIATING} state, which is otherwise unused in vanilla code. + * + *

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

The collected {@link net.minecraftforge.fml.network.NetworkRegistry.LoginPayload} are sent, one per tick, via + * the {@link FMLLoginWrapper#wrapPacket(ResourceLocation, PacketBuffer)} mechanism to the incoming client connection. Each + * packet is indexed via {@link net.minecraft.network.login.client.CPacketCustomPayloadLogin#field_209922_a}, which is + * the only mechanism available for tracking request/response pairs. + * + *

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

Once all packets have been dispatched, we wait for all replies to be received. Once all replies are received, the + * final login phase will commence. + */ +public class FMLHandshakeHandler { + static final Marker FMLHSMARKER = MarkerManager.getMarker("FMLHANDSHAKE").setParents(FMLNetworking.NETWORK); + private static final Logger LOGGER = LogManager.getLogger(); + static final ResourceLocation FML_HANDSHAKE_RESOURCE = new ResourceLocation("fml:handshake"); + private static final AttributeKey FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY = AttributeKey.newInstance("fml:handshake"); + + private static final FMLLoginWrapper loginWrapper = new FMLLoginWrapper(); + private static SimpleChannel channel; + static { + channel = NetworkRegistry.ChannelBuilder.named(FML_HANDSHAKE_RESOURCE). + clientAcceptedVersions(a -> true). + serverAcceptedVersions(a -> true). + networkProtocolVersion(() -> NetworkHooks.NETVERSION). + simpleChannel(); + channel.messageBuilder(FMLHandshakeMessages.C2SAcknowledge.class, 99). + loginIndex(FMLHandshakeMessages.LoginIndexedMessage::getLoginIndex, FMLHandshakeMessages.LoginIndexedMessage::setLoginIndex). + decoder(FMLHandshakeMessages.C2SAcknowledge::decode). + encoder(FMLHandshakeMessages.C2SAcknowledge::encode). + consumer(indexFirst(FMLHandshakeHandler::handleClientAck)). + add(); + channel.messageBuilder(FMLHandshakeMessages.S2CModList.class, 1). + loginIndex(FMLHandshakeMessages.LoginIndexedMessage::getLoginIndex, FMLHandshakeMessages.LoginIndexedMessage::setLoginIndex). + decoder(FMLHandshakeMessages.S2CModList::decode). + encoder(FMLHandshakeMessages.S2CModList::encode). + markAsLoginPacket(). + consumer(biConsumerFor(FMLHandshakeHandler::handleServerModListOnClient)). + add(); + channel.messageBuilder(FMLHandshakeMessages.C2SModListReply.class, 2). + loginIndex(FMLHandshakeMessages.LoginIndexedMessage::getLoginIndex, FMLHandshakeMessages.LoginIndexedMessage::setLoginIndex). + decoder(FMLHandshakeMessages.C2SModListReply::decode). + encoder(FMLHandshakeMessages.C2SModListReply::encode). + consumer(indexFirst(FMLHandshakeHandler::handleClientModListOnServer)). + add(); + channel.messageBuilder(FMLHandshakeMessages.S2CRegistry.class, 3). + loginIndex(FMLHandshakeMessages.LoginIndexedMessage::getLoginIndex, FMLHandshakeMessages.LoginIndexedMessage::setLoginIndex). + decoder(FMLHandshakeMessages.S2CRegistry::decode). + encoder(FMLHandshakeMessages.S2CRegistry::encode). + buildLoginPacketList(RegistryManager::generateRegistryPackets). + consumer(biConsumerFor(FMLHandshakeHandler::handleRegistryMessage)). + add(); + } + + /** + * Transforms a two-argument instance method reference into a {@link BiConsumer} based on the {@link #getHandshake(Supplier)} function. + * + * @param consumer A two argument instance method reference + * @param message type + * @return A {@link BiConsumer} for use in message handling + */ + private static BiConsumer> biConsumerFor(ThreeConsumer> consumer) + { + return (m, c) -> ThreeConsumer.bindArgs(consumer, m, c).accept(getHandshake(c)); + } + + /** + * Transforms a two-argument instance method reference into a {@link BiConsumer} {@link #biConsumerFor(ThreeConsumer)}, first calling the {@link #handleIndexedMessage(FMLHandshakeMessages.LoginIndexedMessage, Supplier)} + * method to handle index tracking. Used for client to server replies. + * @param next The method reference to call after index handling + * @param message type + * @return A {@link BiConsumer} for use in message handling + */ + private static BiConsumer> indexFirst(ThreeConsumer> next) + { + final BiConsumer> loginIndexedMessageSupplierBiConsumer = biConsumerFor(FMLHandshakeHandler::handleIndexedMessage); + return loginIndexedMessageSupplierBiConsumer.andThen(biConsumerFor(next)); + } + + /** + * Create a new handshake instance. Called when connection is first created during the {@link net.minecraft.network.handshake.client.CPacketHandshake} + * handling. + * + * @param manager The network manager for this connection + * @param direction The {@link NetworkDirection} for this connection: {@link NetworkDirection#LOGIN_TO_SERVER} or {@link NetworkDirection#LOGIN_TO_CLIENT} + */ + static void registerHandshake(NetworkManager manager, NetworkDirection direction) { + manager.channel().attr(FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY).compareAndSet(null, new FMLHandshakeHandler(manager, direction)); + } + + /** + * Retrieve the handshake from the {@link NetworkEvent.Context} + * + * @param contextSupplier the {@link NetworkEvent.Context} + * @return The handshake handler for the connection + */ + private static FMLHandshakeHandler getHandshake(Supplier contextSupplier) { + return contextSupplier.get().attr(FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY).get(); + } + + static boolean tickLogin(NetworkManager networkManager) + { + return networkManager.channel().attr(FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY).get().tickServer(); + } + + private List messageList; + + private List sentMessages = new ArrayList<>(); + + private final NetworkDirection direction; + private final NetworkManager manager; + private int packetPosition; + private FMLHandshakeHandler(NetworkManager networkManager, NetworkDirection side) + { + this.direction = side; + this.manager = networkManager; + this.messageList = NetworkRegistry.gatherLoginPayloads(); + LOGGER.debug(FMLHSMARKER, "Starting new modded network connection. Found {} messages to dispatch.", this.messageList.size()); + } + + private void handleServerModListOnClient(FMLHandshakeMessages.S2CModList serverModList, Supplier c) + { + LOGGER.debug(FMLHSMARKER, "Logging into server with mod list [{}]", serverModList.getModList()); + boolean accepted = NetworkRegistry.validateClientChannels(serverModList.getChannels()); + c.get().setPacketHandled(true); + if (!accepted) { + LOGGER.error(FMLHSMARKER, "Terminating connection with server, mismatched mod list"); + c.get().getNetworkManager().closeChannel(new TextComponentString("Connection closed - mismatched mod channel list")); + return; + } + final FMLHandshakeMessages.C2SModListReply reply = new FMLHandshakeMessages.C2SModListReply(); + channel.reply(reply, c.get()); + LOGGER.debug(FMLHSMARKER, "Accepted server connection"); + } + + private void handleIndexedMessage(MSG message, Supplier c) + { + LOGGER.debug(FMLHSMARKER, "Received client indexed reply {} of type {}", message.getLoginIndex(), message.getClass().getName()); + boolean removed = this.sentMessages.removeIf(i->i==message.getLoginIndex()); + if (!removed) { + LOGGER.error(FMLHSMARKER, "Recieved unexpected index {} in client reply", message.getLoginIndex()); + } + } + + private void handleClientModListOnServer(FMLHandshakeMessages.C2SModListReply clientModList, Supplier c) + { + LOGGER.debug(FMLHSMARKER, "Received client connection with modlist [{}]", clientModList.getModList()); + boolean accepted = NetworkRegistry.validateServerChannels(clientModList.getChannels()); + c.get().setPacketHandled(true); + if (!accepted) { + LOGGER.error(FMLHSMARKER, "Terminating connection with client, mismatched mod list"); + c.get().getNetworkManager().closeChannel(new TextComponentString("Connection closed - mismatched mod channel list")); + return; + } + LOGGER.debug(FMLHSMARKER, "Accepted client connection mod list"); + } + private void handleRegistryMessage(final FMLHandshakeMessages.S2CRegistry registryPacket, final Supplier contextSupplier) { + RegistryManager.acceptRegistry(registryPacket, contextSupplier); + contextSupplier.get().setPacketHandled(true); + final FMLHandshakeMessages.C2SAcknowledge reply = new FMLHandshakeMessages.C2SAcknowledge(); + channel.reply(reply, contextSupplier.get()); + } + + private void handleClientAck(final FMLHandshakeMessages.C2SAcknowledge msg, final Supplier contextSupplier) { + LOGGER.debug(FMLHSMARKER, "Received acknowledgement from client"); + contextSupplier.get().setPacketHandled(true); + } + + /** + * FML will send packets, from Server to Client, from the messages queue until the queue is drained. Each message + * will be indexed, and placed into the "pending acknowledgement" queue. + * + * As indexed packets are received at the server, they will be removed from the "pending acknowledgement" queue. + * + * Once the pending queue is drained, this method returns true - indicating that login processing can proceed to + * the next step. + * + * @return true if there is no more need to tick this login connection. + */ + public boolean tickServer() + { + if (packetPosition < messageList.size()) { + NetworkRegistry.LoginPayload message = messageList.get(packetPosition); + + LOGGER.debug(FMLHSMARKER, "Sending ticking packet info '{}' to '{}' sequence {}", message.getMessageContext(), message.getChannelName(), packetPosition); + loginWrapper.sendServerToClientLoginPacket(message.getChannelName(), message.getData(), packetPosition, this.manager); + sentMessages.add(packetPosition); + packetPosition++; + } + + // we're done when sentMessages is empty + if (sentMessages.isEmpty() && packetPosition >= messageList.size()-1) { + // clear ourselves - we're done! + this.manager.channel().attr(FML_HANDSHAKE_HANDLER_ATTRIBUTE_KEY).set(null); + LOGGER.debug(FMLHSMARKER, "Handshake complete!"); + return true; + } + return false; + } +} diff --git a/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessage.java b/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java similarity index 57% rename from src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessage.java rename to src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java index 41f14c08a..333643c6f 100644 --- a/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessage.java +++ b/src/main/java/net/minecraftforge/fml/network/FMLHandshakeMessages.java @@ -5,35 +5,37 @@ import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; import net.minecraft.nbt.NBTTagString; import net.minecraft.network.PacketBuffer; +import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.loading.moddiscovery.ModInfo; +import net.minecraftforge.registries.ForgeRegistry; +import net.minecraftforge.registries.IForgeRegistryEntry; import java.util.List; import java.util.stream.Collectors; -class FMLHandshakeMessage +public class FMLHandshakeMessages { - // Login index sequence number - private int index; - void setPacketIndexSequence(int i) - { - this.index = i; - } + static class LoginIndexedMessage { + private int loginIndex; - int getPacketIndexSequence() - { - return index; - } + void setLoginIndex(final int loginIndex) { + this.loginIndex = loginIndex; + } + int getLoginIndex() { + return loginIndex; + } + } /** * Server to client "list of mods". Always first handshake message. */ - static class S2CModList extends FMLHandshakeMessage + public static class S2CModList extends LoginIndexedMessage { private NBTTagList channels; private List modList; - S2CModList() { + public S2CModList() { this.modList = ModList.get().getMods().stream().map(ModInfo::getModId).collect(Collectors.toList()); } @@ -43,13 +45,13 @@ class FMLHandshakeMessage this.channels = nbtTagCompound.getTagList("channels", 10); } - static S2CModList decode(PacketBuffer packetBuffer) + public static S2CModList decode(PacketBuffer packetBuffer) { final NBTTagCompound nbtTagCompound = packetBuffer.readCompoundTag(); return new S2CModList(nbtTagCompound); } - void encode(PacketBuffer packetBuffer) + public void encode(PacketBuffer packetBuffer) { NBTTagCompound tag = new NBTTagCompound(); tag.setTag("modlist",modList.stream().map(NBTTagString::new).collect(Collectors.toCollection(NBTTagList::new))); @@ -66,9 +68,9 @@ class FMLHandshakeMessage } } - static class C2SModListReply extends S2CModList + public static class C2SModListReply extends S2CModList { - C2SModListReply() { + public C2SModListReply() { super(); } @@ -76,7 +78,7 @@ class FMLHandshakeMessage super(buffer); } - static C2SModListReply decode(PacketBuffer buffer) + public static C2SModListReply decode(PacketBuffer buffer) { return new C2SModListReply(buffer.readCompoundTag()); } @@ -86,4 +88,30 @@ class FMLHandshakeMessage super.encode(buffer); } } + + public static class C2SAcknowledge extends LoginIndexedMessage { + public void encode(PacketBuffer buf) { + + } + + public static C2SAcknowledge decode(PacketBuffer buf) { + return new C2SAcknowledge(); + } + } + + public static class S2CRegistry extends LoginIndexedMessage { + + public S2CRegistry(final ResourceLocation key, final ForgeRegistry> registry) { + } + + S2CRegistry() { + } + + void encode(final PacketBuffer buffer) { + } + + public static S2CRegistry decode(final PacketBuffer buffer) { + return new S2CRegistry(); + } + } } diff --git a/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java b/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java new file mode 100644 index 000000000..9cdf4f8bb --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/network/FMLLoginWrapper.java @@ -0,0 +1,65 @@ +package net.minecraftforge.fml.network; + +import io.netty.buffer.Unpooled; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.network.event.EventNetworkChannel; +import org.apache.commons.lang3.tuple.Pair; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +/** + * Wrapper for custom login packets. Transforms unnamed login channel messages into channels dispatched the same + * as regular custom packets. + */ +public class FMLLoginWrapper { + private static final Logger LOGGER = LogManager.getLogger(); + static final ResourceLocation WRAPPER = new ResourceLocation("fml:loginwrapper"); + private EventNetworkChannel wrapperChannel; + + FMLLoginWrapper() { + wrapperChannel = NetworkRegistry.ChannelBuilder.named(FMLLoginWrapper.WRAPPER). + clientAcceptedVersions(a->true). + serverAcceptedVersions(a->true). + networkProtocolVersion(()->NetworkHooks.NETVERSION) + .eventNetworkChannel(); + wrapperChannel.addListener(this::wrapperReceived); + } + + private void wrapperReceived(final T packet) { + final NetworkEvent.Context wrappedContext = packet.getSource().get(); + final PacketBuffer payload = packet.getPayload(); + ResourceLocation targetNetworkReceiver = FMLHandshakeHandler.FML_HANDSHAKE_RESOURCE; + PacketBuffer data = null; + if (payload != null) { + targetNetworkReceiver = payload.readResourceLocation(); + final int payloadLength = payload.readVarInt(); + data = new PacketBuffer(payload.readBytes(payloadLength)); + } + final int loginSequence = packet.getLoginIndex(); + LOGGER.debug(FMLHandshakeHandler.FMLHSMARKER, "Recieved login wrapper packet event for channel {} with index {}", targetNetworkReceiver, loginSequence); + final NetworkEvent.Context context = new NetworkEvent.Context(wrappedContext.getNetworkManager(), wrappedContext.getDirection(), new PacketDispatcher((rl, buf) -> { + LOGGER.debug(FMLHandshakeHandler.FMLHSMARKER, "Dispatching wrapped packet reply for channel {} with index {}", rl, loginSequence); + wrappedContext.getPacketDispatcher().sendPacket(WRAPPER, this.wrapPacket(rl, buf)); + })); + final NetworkEvent.LoginPayloadEvent loginPayloadEvent = new NetworkEvent.LoginPayloadEvent(data, () -> context, loginSequence); + NetworkRegistry.findTarget(targetNetworkReceiver).ifPresent(ni -> { + ni.dispatchLoginPacket(loginPayloadEvent); + wrappedContext.setPacketHandled(context.getPacketHandled()); + }); + } + + private PacketBuffer wrapPacket(final ResourceLocation rl, final PacketBuffer buf) { + PacketBuffer pb = new PacketBuffer(Unpooled.buffer(buf.capacity())); + pb.writeResourceLocation(rl); + pb.writeVarInt(buf.readableBytes()); + pb.writeBytes(buf); + return pb; + } + + void sendServerToClientLoginPacket(final ResourceLocation resourceLocation, final PacketBuffer buffer, final int index, final NetworkManager manager) { + PacketBuffer pb = wrapPacket(resourceLocation, buffer); + manager.sendPacket(NetworkDirection.LOGIN_TO_CLIENT.buildPacket(Pair.of(pb, index), WRAPPER).getThis()); + } +} diff --git a/src/main/java/net/minecraftforge/fml/network/FMLNetworking.java b/src/main/java/net/minecraftforge/fml/network/FMLNetworking.java index 90d86d956..20e0fd468 100644 --- a/src/main/java/net/minecraftforge/fml/network/FMLNetworking.java +++ b/src/main/java/net/minecraftforge/fml/network/FMLNetworking.java @@ -2,150 +2,14 @@ package net.minecraftforge.fml.network; import io.netty.util.AttributeKey; import net.minecraft.network.NetworkManager; -import net.minecraft.util.text.ITextComponent; -import net.minecraft.util.text.TextComponentString; -import net.minecraftforge.fml.network.simple.SimpleChannel; 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 java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.function.Supplier; - public class FMLNetworking { private static final Logger LOGGER = LogManager.getLogger(); - private static final Marker FMLHSMARKER = MarkerManager.getMarker("FMLHSMARKER"); + static final Marker NETWORK = MarkerManager.getMarker("FMLNETWORK"); static final AttributeKey FML_MARKER = AttributeKey.valueOf("fml:marker"); - private static final AttributeKey FML_HANDSHAKE = AttributeKey.newInstance("fml:handshake"); - - static void registerHandshake(NetworkManager manager, NetworkDirection direction) { - manager.channel().attr(FML_HANDSHAKE).compareAndSet(null, new FMLHandshake(manager, direction)); - } - - private static FMLHandshake getHandshake(Supplier contextSupplier) { - final NetworkManager networkManager = contextSupplier.get().getNetworkManager(); - return getHandshake(networkManager); - } - - private static FMLHandshake getHandshake(NetworkManager manager) - { - return manager.channel().attr(FML_HANDSHAKE).get(); - } - - static boolean dispatch(NetworkManager networkManager) - { - return getHandshake(networkManager).tickServer(); - } - - public static class FMLHandshake { - private static SimpleChannel channel; - private static List> messages = Arrays.asList(FMLHandshakeMessage.S2CModList::new); - private List sentMessages = new ArrayList<>(); - static { - channel = NetworkRegistry.ChannelBuilder.named(NetworkHooks.FMLHANDSHAKE). - clientAcceptedVersions(a -> true). - serverAcceptedVersions(a -> true). - networkProtocolVersion(() -> NetworkHooks.NETVERSION). - simpleChannel(); - channel.messageBuilder(FMLHandshakeMessage.S2CModList.class, 1). - decoder(FMLHandshakeMessage.S2CModList::decode). - encoder(FMLHandshakeMessage.S2CModList::encode). - loginIndex(FMLHandshakeMessage::getPacketIndexSequence, FMLHandshakeMessage::setPacketIndexSequence). - consumer((m,c)->getHandshake(c).handleServerModListOnClient(m, c)). - add(); - channel.messageBuilder(FMLHandshakeMessage.C2SModListReply.class, 2). - loginIndex(FMLHandshakeMessage::getPacketIndexSequence, FMLHandshakeMessage::setPacketIndexSequence). - decoder(FMLHandshakeMessage.C2SModListReply::decode). - encoder(FMLHandshakeMessage.C2SModListReply::encode). - consumer((m,c) -> getHandshake(c).handleClientModListOnServer(m,c)). - add(); - } - - private final NetworkDirection direction; - - private final NetworkManager manager; - private int packetPosition; - - public FMLHandshake(NetworkManager networkManager, NetworkDirection side) - { - this.direction = side; - this.manager = networkManager; - } - - private void handleServerModListOnClient(FMLHandshakeMessage.S2CModList serverModList, Supplier c) - { - LOGGER.debug(FMLHSMARKER, "Logging into server with mod list [{}]", serverModList.getModList()); - boolean accepted = NetworkRegistry.validateClientChannels(serverModList.getChannels()); - c.get().setPacketHandled(true); - if (!accepted) { - LOGGER.error(FMLHSMARKER, "Terminating connection with server, mismatched mod list"); - c.get().getNetworkManager().closeChannel(new TextComponentString("Connection closed - mismatched mod channel list")); - return; - } - final FMLHandshakeMessage.C2SModListReply reply = new FMLHandshakeMessage.C2SModListReply(); - reply.setPacketIndexSequence(serverModList.getPacketIndexSequence()); - channel.reply(reply, c.get()); - LOGGER.debug(FMLHSMARKER, "Sent C2SModListReply packet with index {}", reply.getPacketIndexSequence()); - } - - private void handleClientModListOnServer(FMLHandshakeMessage.C2SModListReply clientModList, Supplier c) - { - LOGGER.debug(FMLHSMARKER, "Received client connection with modlist [{}]", clientModList.getModList()); - final FMLHandshakeMessage message = this.sentMessages.stream().filter(ob -> ob.getPacketIndexSequence() == clientModList.getPacketIndexSequence()).findFirst().orElseThrow(() -> new RuntimeException("Unexpected reply from client")); - boolean removed = this.sentMessages.remove(message); - boolean accepted = NetworkRegistry.validateServerChannels(clientModList.getChannels()); - c.get().setPacketHandled(true); - if (!accepted) { - LOGGER.error(FMLHSMARKER, "Terminating connection with client, mismatched mod list"); - c.get().getNetworkManager().closeChannel(new TextComponentString("Connection closed - mismatched mod channel list")); - return; - } - LOGGER.debug(FMLHSMARKER, "Cleared original message {}", removed); - } - - /** - * Design of handshake. - * - * After {@link net.minecraft.server.network.NetHandlerLoginServer} enters the {@link net.minecraft.server.network.NetHandlerLoginServer.LoginState#NEGOTIATING} - * state, this will be ticked once per server tick. - * - * FML will send packets, from Server to Client, from the messages queue until the queue is drained. Each message - * will be indexed, and placed into the "pending acknowledgement" queue. - * - * The client should send an acknowledgement for every packet that has a positive index, containing - * that index (and maybe other data as well). - * - * As indexed packets are received at the server, they will be removed from the "pending acknowledgement" queue. - * - * Once the pending queue is drained, this method returns true - indicating that login processing can proceed to - * the next step. - * - * @return true if there is no more need to tick this login connection. - */ - public boolean tickServer() - { - if (packetPosition < messages.size()) { - final FMLHandshakeMessage message = messages.get(packetPosition).get(); - message.setPacketIndexSequence(packetPosition); - LOGGER.debug(FMLHSMARKER, "Sending ticking packet {} index {}", message.getClass().getName(), message.getPacketIndexSequence()); - channel.sendTo(message, this.manager, this.direction); - sentMessages.add(message); - packetPosition++; - } - - // we're done when sentMessages is empty - if (sentMessages.isEmpty()) { - // clear ourselves - we're done! - this.manager.channel().attr(FML_HANDSHAKE).set(null); - return true; - } - return false; - } - } - - } diff --git a/src/main/java/net/minecraftforge/fml/network/ICustomPacket.java b/src/main/java/net/minecraftforge/fml/network/ICustomPacket.java index 7f7031728..2ac51fac4 100644 --- a/src/main/java/net/minecraftforge/fml/network/ICustomPacket.java +++ b/src/main/java/net/minecraftforge/fml/network/ICustomPacket.java @@ -61,7 +61,7 @@ public interface ICustomPacket> { } default ResourceLocation getName() { - return Fields.lookup.get(this.getClass()).channel.map(f->UnsafeHacks.getField(f, this)).orElse(NetworkHooks.FMLHANDSHAKE); + return Fields.lookup.get(this.getClass()).channel.map(f->UnsafeHacks.getField(f, this)).orElse(FMLLoginWrapper.WRAPPER); } default int getIndex() { diff --git a/src/main/java/net/minecraftforge/fml/network/NetworkEvent.java b/src/main/java/net/minecraftforge/fml/network/NetworkEvent.java index ae48e4258..81bc66ffe 100644 --- a/src/main/java/net/minecraftforge/fml/network/NetworkEvent.java +++ b/src/main/java/net/minecraftforge/fml/network/NetworkEvent.java @@ -20,12 +20,18 @@ package net.minecraftforge.fml.network; import com.google.common.util.concurrent.ListenableFuture; +import io.netty.util.Attribute; +import io.netty.util.AttributeKey; import net.minecraft.network.NetworkManager; import net.minecraft.network.PacketBuffer; import net.minecraft.util.IThreadListener; +import net.minecraft.util.ResourceLocation; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.fml.LogicalSidedProvider; +import org.apache.commons.lang3.tuple.Pair; +import java.util.ArrayList; +import java.util.List; import java.util.function.Supplier; public class NetworkEvent extends Event @@ -41,6 +47,13 @@ public class NetworkEvent extends Event this.loginIndex = payload.getIndex(); } + private NetworkEvent(final PacketBuffer payload, final Supplier source, final int loginIndex) + { + this.payload = payload; + this.source = source; + this.loginIndex = loginIndex; + } + public PacketBuffer getPayload() { return payload; @@ -82,6 +95,23 @@ public class NetworkEvent extends Event } } + public static class GatherLoginPayloadsEvent extends Event { + private final List collected; + + public GatherLoginPayloadsEvent(final List loginPayloadList) { + this.collected = loginPayloadList; + } + + public void add(PacketBuffer buffer, ResourceLocation channelName, String context) { + collected.add(new NetworkRegistry.LoginPayload(buffer, channelName, context)); + } + } + + public static class LoginPayloadEvent extends NetworkEvent { + LoginPayloadEvent(final PacketBuffer payload, final Supplier source, final int loginIndex) { + super(payload, source, loginIndex); + } + } /** * Context for {@link NetworkEvent} */ @@ -96,20 +126,34 @@ public class NetworkEvent extends Event * The {@link NetworkDirection} this message has been received on. */ private final NetworkDirection side; + + /** + * The packet dispatcher for this event. Sends back to the origin. + */ + private final PacketDispatcher packetDispatcher; private boolean packetHandled; - Context(NetworkManager netHandler, NetworkDirection side) + Context(NetworkManager netHandler, NetworkDirection side, int index) { - this.networkManager = netHandler; + this(netHandler, side, new PacketDispatcher.NetworkManagerDispatcher(netHandler, index, side.reply()::buildPacket)); + } + + Context(NetworkManager networkManager, NetworkDirection side, PacketDispatcher dispatcher) { + this.networkManager = networkManager; this.side = side; + this.packetDispatcher = dispatcher; } public NetworkDirection getDirection() { return side; } - public NetworkManager getNetworkManager() { - return networkManager; + public PacketDispatcher getPacketDispatcher() { + return packetDispatcher; + } + + public Attribute attr(AttributeKey key) { + return networkManager.channel().attr(key); } public void setPacketHandled(boolean packetHandled) { @@ -125,5 +169,9 @@ public class NetworkEvent extends Event public ListenableFuture enqueueWork(Runnable runnable) { return (ListenableFuture)LogicalSidedProvider.WORKQUEUE.get(getDirection().getLogicalSide()).addScheduledTask(runnable); } + + NetworkManager getNetworkManager() { + return networkManager; + } } } diff --git a/src/main/java/net/minecraftforge/fml/network/NetworkHooks.java b/src/main/java/net/minecraftforge/fml/network/NetworkHooks.java index 97b121fc3..7c5f490c7 100644 --- a/src/main/java/net/minecraftforge/fml/network/NetworkHooks.java +++ b/src/main/java/net/minecraftforge/fml/network/NetworkHooks.java @@ -25,7 +25,6 @@ import net.minecraft.network.NetHandlerPlayServer; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.handshake.client.CPacketHandshake; -import net.minecraft.network.login.client.CPacketCustomPayloadLogin; import net.minecraft.server.network.NetHandlerLoginServer; import net.minecraft.util.ResourceLocation; @@ -35,7 +34,7 @@ public class NetworkHooks { public static final String NETVERSION = "FML1"; public static final String NOVERSION = "NONE"; - static final ResourceLocation FMLHANDSHAKE = new ResourceLocation("fml", "handshake"); + public static String getFMLVersion(final String ip) { return ip.contains("\0") ? Objects.equals(ip.split("\0")[1], NETVERSION) ? NETVERSION : ip.split("\0")[1] : NOVERSION; @@ -43,7 +42,7 @@ public class NetworkHooks public static boolean accepts(final CPacketHandshake packet) { - return Objects.equals(packet.getFMLVersion(), NETVERSION); + return Objects.equals(packet.getFMLVersion(), NETVERSION) || NetworkRegistry.acceptsVanillaConnections(); } public static ConnectionType getConnectionType(final NetHandlerPlayServer connection) @@ -64,17 +63,17 @@ public class NetworkHooks public static void registerServerLoginChannel(NetworkManager manager, CPacketHandshake packet) { manager.channel().attr(FMLNetworking.FML_MARKER).set(packet.getFMLVersion()); - FMLNetworking.registerHandshake(manager, NetworkDirection.LOGIN_TO_CLIENT); + FMLHandshakeHandler.registerHandshake(manager, NetworkDirection.LOGIN_TO_CLIENT); } public static void registerClientLoginChannel(NetworkManager manager) { manager.channel().attr(FMLNetworking.FML_MARKER).set(NETVERSION); - FMLNetworking.registerHandshake(manager, NetworkDirection.LOGIN_TO_SERVER); + FMLHandshakeHandler.registerHandshake(manager, NetworkDirection.LOGIN_TO_SERVER); } public static boolean tickNegotiation(NetHandlerLoginServer netHandlerLoginServer, NetworkManager networkManager, EntityPlayerMP player) { - return FMLNetworking.dispatch(networkManager); + return FMLHandshakeHandler.tickLogin(networkManager); } } diff --git a/src/main/java/net/minecraftforge/fml/network/NetworkInstance.java b/src/main/java/net/minecraftforge/fml/network/NetworkInstance.java index b8efef78a..28d34209b 100644 --- a/src/main/java/net/minecraftforge/fml/network/NetworkInstance.java +++ b/src/main/java/net/minecraftforge/fml/network/NetworkInstance.java @@ -19,14 +19,15 @@ package net.minecraftforge.fml.network; -import net.minecraft.nbt.INBTBase; import net.minecraft.network.NetworkManager; import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.eventbus.api.Event; import net.minecraftforge.eventbus.api.IEventBus; import net.minecraftforge.eventbus.api.IEventListener; +import org.apache.commons.lang3.tuple.Pair; +import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; import java.util.function.Supplier; @@ -63,6 +64,11 @@ public class NetworkInstance this.networkEventBus.addListener(eventListener); } + public void addGatherListener(Consumer eventListener) + { + this.networkEventBus.addListener(eventListener); + } + public void registerObject(final Object object) { this.networkEventBus.register(object); } @@ -73,7 +79,7 @@ public class NetworkInstance boolean dispatch(final NetworkDirection side, final ICustomPacket packet, final NetworkManager manager) { - final NetworkEvent.Context context = new NetworkEvent.Context(manager, side); + final NetworkEvent.Context context = new NetworkEvent.Context(manager, side, packet.getIndex()); this.networkEventBus.post(side.getEvent(packet, () -> context)); return context.getPacketHandled(); } @@ -90,4 +96,12 @@ public class NetworkInstance boolean tryClientVersionOnServer(final String clientVersion) { return this.serverAcceptedVersions.test(clientVersion); } + + void dispatchGatherLogin(final List loginPayloadList) { + this.networkEventBus.post(new NetworkEvent.GatherLoginPayloadsEvent(loginPayloadList)); + } + + void dispatchLoginPacket(final NetworkEvent.LoginPayloadEvent loginPayloadEvent) { + this.networkEventBus.post(loginPayloadEvent); + } } diff --git a/src/main/java/net/minecraftforge/fml/network/NetworkRegistry.java b/src/main/java/net/minecraftforge/fml/network/NetworkRegistry.java index 743911979..dd27ebb46 100644 --- a/src/main/java/net/minecraftforge/fml/network/NetworkRegistry.java +++ b/src/main/java/net/minecraftforge/fml/network/NetworkRegistry.java @@ -21,6 +21,8 @@ package net.minecraftforge.fml.network; import net.minecraft.nbt.NBTTagCompound; import net.minecraft.nbt.NBTTagList; +import net.minecraft.network.NetworkManager; +import net.minecraft.network.PacketBuffer; import net.minecraft.util.ResourceLocation; import net.minecraftforge.fml.network.event.EventNetworkChannel; import net.minecraftforge.fml.network.simple.SimpleChannel; @@ -30,34 +32,80 @@ import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.MarkerManager; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; +import java.util.*; +import java.util.function.BiFunction; import java.util.function.Predicate; import java.util.function.Supplier; import java.util.stream.Collectors; +/** + * The network registry. Tracks channels on behalf of mods. + */ public class NetworkRegistry { private static final Logger LOGGER = LogManager.getLogger(); private static final Marker NETREGISTRY = MarkerManager.getMarker("NETREGISTRY"); private static Map instances = new HashMap<>(); + + /** + * Special value for clientAcceptedVersions and serverAcceptedVersions predicates indicating the other side lacks + * this channel. + */ + @SuppressWarnings("RedundantStringConstructorCall") + public static String ABSENT = new String("ABSENT \uD83E\uDD14"); + public static List getNonVanillaNetworkMods() { - return Collections.emptyList(); + return instances.keySet().stream().map(Object::toString).collect(Collectors.toList()); + } + + static boolean acceptsVanillaConnections() { + return instances.isEmpty(); } + /** + * Create a new {@link SimpleChannel}. + * + * @param name The registry name for this channel. Must be unique + * @param networkProtocolVersion The network protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} + * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} + * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} + * @return A new {@link SimpleChannel} + * + * @see ChannelBuilder#newSimpleChannel(ResourceLocation, Supplier, Predicate, Predicate) + */ public static SimpleChannel newSimpleChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { return new SimpleChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); } + /** + * Create a new {@link EventNetworkChannel}. + * + * @param name The registry name for this channel. Must be unique + * @param networkProtocolVersion The network protocol version string that will be offered to the remote side {@link ChannelBuilder#networkProtocolVersion(Supplier)} + * @param clientAcceptedVersions Called on the client with the networkProtocolVersion string from the server {@link ChannelBuilder#clientAcceptedVersions(Predicate)} + * @param serverAcceptedVersions Called on the server with the networkProtocolVersion string from the client {@link ChannelBuilder#serverAcceptedVersions(Predicate)} + + * @return A new {@link EventNetworkChannel} + * + * @see ChannelBuilder#newEventChannel(ResourceLocation, Supplier, Predicate, Predicate) + */ public static EventNetworkChannel newEventChannel(final ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { return new EventNetworkChannel(createInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions)); } + + + /** + * Creates the internal {@link NetworkInstance} that tracks the channel data. + * @param name registry name + * @param networkProtocolVersion The protocol version string + * @param clientAcceptedVersions The client accepted predicate + * @param serverAcceptedVersions The server accepted predicate + * @return The {@link NetworkInstance} + * @throws IllegalArgumentException if the name already exists + */ private static NetworkInstance createInstance(ResourceLocation name, Supplier networkProtocolVersion, Predicate clientAcceptedVersions, Predicate serverAcceptedVersions) { final NetworkInstance networkInstance = new NetworkInstance(name, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); @@ -69,11 +117,25 @@ public class NetworkRegistry return networkInstance; } + /** + * Find the {@link NetworkInstance}, if possible + * + * @param resourceLocation The network instance to lookup + * @return The {@link Optional} {@link NetworkInstance} + */ static Optional findTarget(ResourceLocation resourceLocation) { return Optional.ofNullable(instances.get(resourceLocation)); } + /** + * Construct the NBT representation of the channel list, for use during login handshaking + * + * @see FMLHandshakeMessages.S2CModList + * @see FMLHandshakeMessages.C2SModListReply + * + * @return An nbt tag list + */ static NBTTagList buildChannelVersions() { return instances.entrySet().stream().map(e-> { final NBTTagCompound tag = new NBTTagCompound(); @@ -83,50 +145,120 @@ public class NetworkRegistry }).collect(Collectors.toCollection(NBTTagList::new)); } + /** + * Validate the channels from the server on the client. Tests the client predicates against the server + * supplied network protocol version. + * + * @param channels An @{@link NBTTagList} of name->version pairs for testing + * @return true if all channels accept themselves + */ static boolean validateClientChannels(final NBTTagList channels) { - final List> results = channels.stream().map(t -> { - NBTTagCompound tag = (NBTTagCompound) t; - final ResourceLocation rl = new ResourceLocation(tag.getString("name")); - final String serverVersion = tag.getString("version"); - boolean test = instances.get(rl).tryServerVersionOnClient(serverVersion); - LOGGER.debug(NETREGISTRY, "Channel {} : Client version test of ''{}'' from server : {}", rl, serverVersion, test); - return Pair.of(rl, test); - }).filter(p->!p.getRight()).collect(Collectors.toList()); - - if (!results.isEmpty()) { - LOGGER.error(NETREGISTRY, "Channels [{}] rejected their server side version number", - results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(","))); - return false; - } - LOGGER.debug(NETREGISTRY, "Accepting channel list from server"); - return true; + return validateChannels(channels, "server", NetworkInstance::tryServerVersionOnClient); } + /** + * Validate the channels from the client on the server. Tests the server predicates against the client + * supplied network protocol version. + * @param channels An @{@link NBTTagList} of name->version pairs for testing + * @return true if all channels accept themselves + */ static boolean validateServerChannels(final NBTTagList channels) { - final List> results = channels.stream().map(t -> { - NBTTagCompound tag = (NBTTagCompound) t; - final ResourceLocation rl = new ResourceLocation(tag.getString("name")); - final String clientVersion = tag.getString("version"); - boolean test = instances.get(rl).tryClientVersionOnServer(clientVersion); - LOGGER.debug(NETREGISTRY, "Channel {} : Server version test of ''{}'' from client : {}", rl, clientVersion, test); - return Pair.of(rl, test); - }).filter(p->!p.getRight()).collect(Collectors.toList()); + return validateChannels(channels, "client", NetworkInstance::tryClientVersionOnServer); + } + + /** + * Tests if the nbt list matches with the supplied predicate tester + * + * @param channels An @{@link NBTTagList} of name->version pairs for testing + * @param originName A label for use in logging (where the version pairs came from) + * @param testFunction The test function to use for testing + * @return true if all channels accept themselves + */ + private static boolean validateChannels(final NBTTagList channels, final String originName, BiFunction testFunction) { + Map incoming = channels.stream().map(NBTTagCompound.class::cast).collect(Collectors.toMap(tag->new ResourceLocation(tag.getString("name")),tag->tag.getString("version"))); + + final List> results = instances.values().stream(). + map(ni -> { + final String incomingVersion = incoming.getOrDefault(ni.getChannelName(), ABSENT); + final boolean test = testFunction.apply(ni, incomingVersion); + LOGGER.debug(NETREGISTRY, "Channel '{}' : Version test of '{}' from {} : {}", ni.getChannelName(), incomingVersion, originName, test ? "ACCEPTED" : "REJECTED"); + return Pair.of(ni.getChannelName(), test); + }).filter(p->!p.getRight()).collect(Collectors.toList()); if (!results.isEmpty()) { - LOGGER.error(NETREGISTRY, "Channels [{}] rejected their client side version number", - results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(","))); + LOGGER.error(NETREGISTRY, "Channels [{}] rejected their {} side version number", + results.stream().map(Pair::getLeft).map(Object::toString).collect(Collectors.joining(",")), + originName); return false; } - LOGGER.debug(NETREGISTRY, "Accepting channel list from client"); + LOGGER.debug(NETREGISTRY, "Accepting channel list from {}", originName); return true; } + /** + * Retrieve the {@link LoginPayload} list for dispatch during {@link FMLHandshakeHandler#tickLogin(NetworkManager)} handling. + * Dispatches {@link net.minecraftforge.fml.network.NetworkEvent.GatherLoginPayloadsEvent} to each {@link NetworkInstance}. + * + * @return The {@link LoginPayload} list + */ + static List gatherLoginPayloads() { + List gatheredPayloads = new ArrayList<>(); + instances.values().forEach(ni->ni.dispatchGatherLogin(gatheredPayloads)); + return gatheredPayloads; + } + + /** + * Tracks individual outbound messages for dispatch to clients during login handling. Gathered by dispatching + * {@link net.minecraftforge.fml.network.NetworkEvent.GatherLoginPayloadsEvent} during early connection handling. + */ + public static class LoginPayload { + /** + * The data for sending + */ + private final PacketBuffer data; + /** + * A channel which will receive a {@link NetworkEvent.LoginPayloadEvent} from the {@link FMLLoginWrapper} + */ + private final ResourceLocation channelName; + + /** + * Some context for logging purposes + */ + private final String messageContext; + + public LoginPayload(final PacketBuffer buffer, final ResourceLocation channelName, final String messageContext) { + this.data = buffer; + this.channelName = channelName; + this.messageContext = messageContext; + } + + public PacketBuffer getData() { + return data; + } + + public ResourceLocation getChannelName() { + return channelName; + } + + public String getMessageContext() { + return messageContext; + } + } + + /** + * Builder for constructing network channels using a builder style API. + */ public static class ChannelBuilder { private ResourceLocation channelName; private Supplier networkProtocolVersion; private Predicate clientAcceptedVersions; private Predicate serverAcceptedVersions; + /** + * The name of the channel. Must be unique. + * @param channelName The name of the channel + * @return the channel builder + */ public static ChannelBuilder named(ResourceLocation channelName) { ChannelBuilder builder = new ChannelBuilder(); @@ -134,32 +266,68 @@ public class NetworkRegistry return builder; } + /** + * The network protocol string for this channel. This will be gathered during login and sent to + * the remote partner, where it will be tested with against the relevant predicate. + * + * @see #serverAcceptedVersions(Predicate) + * @see #clientAcceptedVersions(Predicate) + * @param networkProtocolVersion A supplier of strings for network protocol version testing + * @return the channel builder + */ public ChannelBuilder networkProtocolVersion(Supplier networkProtocolVersion) { this.networkProtocolVersion = networkProtocolVersion; return this; } + /** + * A predicate run on the client, with the {@link #networkProtocolVersion(Supplier)} string from + * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of + * the channel on the remote side. + * @param clientAcceptedVersions A predicate for testing + * @return the channel builder + */ public ChannelBuilder clientAcceptedVersions(Predicate clientAcceptedVersions) { this.clientAcceptedVersions = clientAcceptedVersions; return this; } + /** + * A predicate run on the server, with the {@link #networkProtocolVersion(Supplier)} string from + * the server, or the special value {@link NetworkRegistry#ABSENT} indicating the absence of + * the channel on the remote side. + * @param serverAcceptedVersions A predicate for testing + * @return the channel builder + */ public ChannelBuilder serverAcceptedVersions(Predicate serverAcceptedVersions) { this.serverAcceptedVersions = serverAcceptedVersions; return this; } + /** + * Create the network instance + * @return the {@link NetworkInstance} + */ private NetworkInstance createNetworkInstance() { return createInstance(channelName, networkProtocolVersion, clientAcceptedVersions, serverAcceptedVersions); } + /** + * Build a new {@link SimpleChannel} with this builder's configuration. + * + * @return A new {@link SimpleChannel} + */ public SimpleChannel simpleChannel() { return new SimpleChannel(createNetworkInstance()); } + /** + * Build a new {@link EventNetworkChannel} with this builder's configuration. + * @return A new {@link EventNetworkChannel} + */ public EventNetworkChannel eventNetworkChannel() { return new EventNetworkChannel(createNetworkInstance()); } diff --git a/src/main/java/net/minecraftforge/fml/network/PacketDispatcher.java b/src/main/java/net/minecraftforge/fml/network/PacketDispatcher.java new file mode 100644 index 000000000..05fa954d7 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/network/PacketDispatcher.java @@ -0,0 +1,48 @@ +package net.minecraftforge.fml.network; + +import net.minecraft.network.NetworkManager; +import net.minecraft.network.PacketBuffer; +import net.minecraft.util.ResourceLocation; +import org.apache.commons.lang3.tuple.Pair; + +import java.util.function.BiConsumer; +import java.util.function.BiFunction; + +/** + * Dispatcher for sending packets in response to a received packet. Abstracts out the difference between wrapped packets + * and unwrapped packets. + */ +public class PacketDispatcher { + BiConsumer packetSink; + + PacketDispatcher(final BiConsumer packetSink) { + this.packetSink = packetSink; + } + + private PacketDispatcher() { + + } + + public void sendPacket(ResourceLocation resourceLocation, PacketBuffer buffer) { + packetSink.accept(resourceLocation, buffer); + } + + static class NetworkManagerDispatcher extends PacketDispatcher { + private final NetworkManager manager; + private final int packetIndex; + private final BiFunction, ResourceLocation, ICustomPacket> customPacketSupplier; + + NetworkManagerDispatcher(NetworkManager manager, int packetIndex, BiFunction, ResourceLocation, ICustomPacket> customPacketSupplier) { + super(); + this.packetSink = this::dispatchPacket; + this.manager = manager; + this.packetIndex = packetIndex; + this.customPacketSupplier = customPacketSupplier; + } + + private void dispatchPacket(final ResourceLocation resourceLocation, final PacketBuffer buffer) { + final ICustomPacket packet = this.customPacketSupplier.apply(Pair.of(buffer, packetIndex), resourceLocation); + this.manager.sendPacket(packet.getThis()); + } + } +} diff --git a/src/main/java/net/minecraftforge/fml/network/simple/IndexedMessageCodec.java b/src/main/java/net/minecraftforge/fml/network/simple/IndexedMessageCodec.java index 9bcfa9d84..ebb2317c0 100644 --- a/src/main/java/net/minecraftforge/fml/network/simple/IndexedMessageCodec.java +++ b/src/main/java/net/minecraftforge/fml/network/simple/IndexedMessageCodec.java @@ -45,6 +45,11 @@ public class IndexedMessageCodec return (MessageHandler) types.get(msgToReply.getClass()); } + @SuppressWarnings("unchecked") + MessageHandler findIndex(final short i) { + return (MessageHandler) indicies.get(i); + } + @SuppressWarnings("OptionalUsedAsFieldOrParameterType") class MessageHandler { @@ -85,6 +90,15 @@ public class IndexedMessageCodec public Optional> getLoginIndexGetter() { return this.loginIndexGetter; } + + MSG newInstance() { + try { + return messageType.newInstance(); + } catch (InstantiationException | IllegalAccessException e) { + LOGGER.error("Invalid login message", e); + throw new RuntimeException(e); + } + } } private static void tryDecode(PacketBuffer payload, Supplier context, int payloadIndex, MessageHandler codec) diff --git a/src/main/java/net/minecraftforge/fml/network/simple/SimpleChannel.java b/src/main/java/net/minecraftforge/fml/network/simple/SimpleChannel.java index f413dabe3..e644de246 100644 --- a/src/main/java/net/minecraftforge/fml/network/simple/SimpleChannel.java +++ b/src/main/java/net/minecraftforge/fml/network/simple/SimpleChannel.java @@ -24,12 +24,12 @@ import net.minecraft.client.Minecraft; import net.minecraft.network.NetworkManager; import net.minecraft.network.Packet; import net.minecraft.network.PacketBuffer; -import net.minecraftforge.fml.network.ICustomPacket; -import net.minecraftforge.fml.network.NetworkDirection; -import net.minecraftforge.fml.network.NetworkEvent; -import net.minecraftforge.fml.network.NetworkInstance; +import net.minecraftforge.fml.network.*; import org.apache.commons.lang3.tuple.Pair; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; import java.util.function.BiConsumer; import java.util.function.Function; import java.util.function.Supplier; @@ -38,14 +38,26 @@ public class SimpleChannel { private final NetworkInstance instance; private final IndexedMessageCodec indexedCodec; + private List>>> loginPackets; public SimpleChannel(NetworkInstance instance) { this.instance = instance; this.indexedCodec = new IndexedMessageCodec(); + this.loginPackets = new ArrayList<>(); instance.addListener(this::networkEventListener); + instance.addGatherListener(this::networkLoginGather); } + private void networkLoginGather(final NetworkEvent.GatherLoginPayloadsEvent gatherEvent) { + loginPackets.forEach(packetGenerator->{ + packetGenerator.get().forEach(p->{ + PacketBuffer pb = new PacketBuffer(Unpooled.buffer()); + this.indexedCodec.build(p.getRight(), pb); + gatherEvent.add(pb, this.instance.getChannelName(), p.getLeft()); + }); + }); + } private void networkEventListener(final NetworkEvent networkEvent) { this.indexedCodec.consume(networkEvent.getPayload(), networkEvent.getLoginIndex(), networkEvent.getSource()); @@ -77,7 +89,7 @@ public class SimpleChannel public void reply(MSG msgToReply, NetworkEvent.Context context) { - sendTo(msgToReply, context.getNetworkManager(), context.getDirection().reply()); + context.getPacketDispatcher().sendPacket(instance.getChannelName(), toBuffer(msgToReply).getLeft()); } public MessageBuilder messageBuilder(final Class type, int id) { @@ -93,6 +105,7 @@ public class SimpleChannel private BiConsumer> consumer; private Function loginIndexGetter; private BiConsumer loginIndexSetter; + private Supplier>> loginPacketGenerators; private static MessageBuilder forType(final SimpleChannel channel, final Class type, int id) { MessageBuilder builder = new MessageBuilder<>(); @@ -117,6 +130,24 @@ public class SimpleChannel this.loginIndexSetter = loginIndexSetter; return this; } + + public MessageBuilder buildLoginPacketList(Supplier>> loginPacketGenerators) { + this.loginPacketGenerators = loginPacketGenerators; + return this; + } + + public MessageBuilder markAsLoginPacket() + { + this.loginPacketGenerators = () -> { + try { + return Collections.singletonList(Pair.of(type.getName(), type.newInstance())); + } catch (InstantiationException | IllegalAccessException e) { + throw new RuntimeException("Inaccessible no-arg constructor for message "+type.getName(),e); + } + }; + return this; + } + public MessageBuilder consumer(BiConsumer> consumer) { this.consumer = consumer; return this; @@ -130,6 +161,9 @@ public class SimpleChannel if (this.loginIndexGetter != null) { message.setLoginIndexGetter(this.loginIndexGetter); } + if (this.loginPacketGenerators != null) { + this.channel.loginPackets.add(this.loginPacketGenerators); + } } } } diff --git a/src/main/java/net/minecraftforge/fml/util/ThreeConsumer.java b/src/main/java/net/minecraftforge/fml/util/ThreeConsumer.java new file mode 100644 index 000000000..aafd74429 --- /dev/null +++ b/src/main/java/net/minecraftforge/fml/util/ThreeConsumer.java @@ -0,0 +1,28 @@ +package net.minecraftforge.fml.util; + +import java.util.function.Consumer; + +/** + * Three-consumer version of consumer. Allows wrapping methods with three arguments. + */ +public interface ThreeConsumer +{ + + /** + * Bind arguments to the three consumer to generate a consumer. + * + *

+     * {@code
+     * ThreeConsumer.bindArgs(MyClass::instanceMethodReference, arg1, arg2).apply(myClassInstance)
+     * }
+     * 
+ * + * @return a Consumer which has the second and third arguments bound. + */ + static Consumer bindArgs(ThreeConsumer c, U arg2, V arg3) + { + return (arg1) -> c.accept(arg1, arg2, arg3); + } + + void accept(T t, U u, V v); +} diff --git a/src/main/java/net/minecraftforge/registries/RegistryManager.java b/src/main/java/net/minecraftforge/registries/RegistryManager.java index 3778fe22d..be9c04a0b 100644 --- a/src/main/java/net/minecraftforge/registries/RegistryManager.java +++ b/src/main/java/net/minecraftforge/registries/RegistryManager.java @@ -19,8 +19,11 @@ package net.minecraftforge.registries; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Supplier; +import java.util.stream.Collectors; import javax.annotation.Nullable; @@ -31,8 +34,11 @@ import com.google.common.collect.Sets; import com.google.common.collect.Sets.SetView; import net.minecraft.util.ResourceLocation; +import net.minecraftforge.fml.network.FMLHandshakeMessages; +import net.minecraftforge.fml.network.NetworkEvent; import net.minecraftforge.registries.ForgeRegistry.Snapshot; import net.minecraftforge.registries.IForgeRegistry.*; +import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -145,4 +151,14 @@ public class RegistryManager this.registries.clear(); this.superTypes.clear(); } + + public static List> generateRegistryPackets() { + return ACTIVE.registries.entrySet().stream(). + map(e->Pair.of("Registry "+e.getKey(), new FMLHandshakeMessages.S2CRegistry(e.getKey(), e.getValue()))). + collect(Collectors.toList()); + } + + public static void acceptRegistry(final FMLHandshakeMessages.S2CRegistry registryUpdate, final Supplier contextSupplier) { + LOGGER.debug("Received registry packet"); + } }