diff --git a/build.gradle b/build.gradle
index 00f06c1315..07a8594837 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 775a1258cb..736b9ea1ee 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 9435c63070..44e4b94b25 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 0000000000..07015316c4
--- /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 41f14c08ac..333643c6fd 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 extends IForgeRegistryEntry>> 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 0000000000..9cdf4f8bb5
--- /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 90d86d956b..20e0fd4682 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 7f70317287..2ac51fac40 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 ae48e42589..81bc66ffe2 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 97b121fc39..7c5f490c75 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 b8efef78a7..28d34209b0 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 743911979d..dd27ebb46a 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 0000000000..05fa954d7f
--- /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 9bcfa9d841..ebb2317c02 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 f413dabe39..e644de2465 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 0000000000..aafd74429c
--- /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 super T, U, V> 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 3778fe22d7..be9c04a0b9 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");
+ }
}