Net handling pass 2. Tidied up login packet sourcing, generate registry packets.

This commit is contained in:
cpw 2018-09-16 20:54:03 -04:00
parent 9f2c7c881c
commit abacf8e141
17 changed files with 787 additions and 220 deletions

View File

@ -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 {

View File

@ -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);
}

View File

@ -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");

View File

@ -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.
*
* <p>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}.
*
* <p>The {@link #channel} is a {@link SimpleChannel} with standard messages flowing in both directions.
*
* <p>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.
*
* <p>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.
*
* <p>During client to server initiation, on the <em>server</em>, 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.
*
* <p>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.
*
* <p>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.
*
* <p>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<FMLHandshakeHandler> 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 <MSG> message type
* @return A {@link BiConsumer} for use in message handling
*/
private static <MSG extends FMLHandshakeMessages.LoginIndexedMessage> BiConsumer<MSG, Supplier<NetworkEvent.Context>> biConsumerFor(ThreeConsumer<FMLHandshakeHandler, ? super MSG, ? super Supplier<NetworkEvent.Context>> 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 <MSG> message type
* @return A {@link BiConsumer} for use in message handling
*/
private static <MSG extends FMLHandshakeMessages.LoginIndexedMessage> BiConsumer<MSG, Supplier<NetworkEvent.Context>> indexFirst(ThreeConsumer<FMLHandshakeHandler, MSG, Supplier<NetworkEvent.Context>> next)
{
final BiConsumer<MSG, Supplier<NetworkEvent.Context>> 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<NetworkEvent.Context> 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<NetworkRegistry.LoginPayload> messageList;
private List<Integer> 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<NetworkEvent.Context> 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 <MSG extends FMLHandshakeMessages.LoginIndexedMessage> void handleIndexedMessage(MSG message, Supplier<NetworkEvent.Context> 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<NetworkEvent.Context> 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<NetworkEvent.Context> 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<NetworkEvent.Context> 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;
}
}

View File

@ -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<String> 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();
}
}
}

View File

@ -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 <T extends NetworkEvent> 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());
}
}

View File

@ -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<String> FML_MARKER = AttributeKey.valueOf("fml:marker");
private static final AttributeKey<FMLHandshake> 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<NetworkEvent.Context> 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<Supplier<FMLHandshakeMessage>> messages = Arrays.asList(FMLHandshakeMessage.S2CModList::new);
private List<FMLHandshakeMessage> 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<NetworkEvent.Context> 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<NetworkEvent.Context> 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;
}
}
}

View File

@ -61,7 +61,7 @@ public interface ICustomPacket<T extends Packet<?>> {
}
default ResourceLocation getName() {
return Fields.lookup.get(this.getClass()).channel.map(f->UnsafeHacks.<ResourceLocation>getField(f, this)).orElse(NetworkHooks.FMLHANDSHAKE);
return Fields.lookup.get(this.getClass()).channel.map(f->UnsafeHacks.<ResourceLocation>getField(f, this)).orElse(FMLLoginWrapper.WRAPPER);
}
default int getIndex() {

View File

@ -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<Context> 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<NetworkRegistry.LoginPayload> collected;
public GatherLoginPayloadsEvent(final List<NetworkRegistry.LoginPayload> 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<Context> 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 <T> Attribute<T> attr(AttributeKey<T> key) {
return networkManager.channel().attr(key);
}
public void setPacketHandled(boolean packetHandled) {
@ -125,5 +169,9 @@ public class NetworkEvent extends Event
public <V> ListenableFuture<V> enqueueWork(Runnable runnable) {
return (ListenableFuture<V>)LogicalSidedProvider.WORKQUEUE.<IThreadListener>get(getDirection().getLogicalSide()).addScheduledTask(runnable);
}
NetworkManager getNetworkManager() {
return networkManager;
}
}
}

View File

@ -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);
}
}

View File

@ -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<NetworkEvent.GatherLoginPayloadsEvent> 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<NetworkRegistry.LoginPayload> loginPayloadList) {
this.networkEventBus.post(new NetworkEvent.GatherLoginPayloadsEvent(loginPayloadList));
}
void dispatchLoginPacket(final NetworkEvent.LoginPayloadEvent loginPayloadEvent) {
this.networkEventBus.post(loginPayloadEvent);
}
}

View File

@ -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<ResourceLocation, NetworkInstance> 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<String> 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<String> networkProtocolVersion, Predicate<String> clientAcceptedVersions, Predicate<String> 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<String> networkProtocolVersion, Predicate<String> clientAcceptedVersions, Predicate<String> 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<String> networkProtocolVersion, Predicate<String> clientAcceptedVersions, Predicate<String> 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<NetworkInstance> 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<Pair<ResourceLocation, Boolean>> 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<Pair<ResourceLocation, Boolean>> 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<NetworkInstance, String, Boolean> testFunction) {
Map<ResourceLocation, String> incoming = channels.stream().map(NBTTagCompound.class::cast).collect(Collectors.toMap(tag->new ResourceLocation(tag.getString("name")),tag->tag.getString("version")));
final List<Pair<ResourceLocation, Boolean>> 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<LoginPayload> gatherLoginPayloads() {
List<LoginPayload> 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<String> networkProtocolVersion;
private Predicate<String> clientAcceptedVersions;
private Predicate<String> 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<String> 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<String> 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<String> 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());
}

View File

@ -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<ResourceLocation, PacketBuffer> packetSink;
PacketDispatcher(final BiConsumer<ResourceLocation, PacketBuffer> 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<Pair<PacketBuffer, Integer>, ResourceLocation, ICustomPacket<?>> customPacketSupplier;
NetworkManagerDispatcher(NetworkManager manager, int packetIndex, BiFunction<Pair<PacketBuffer, Integer>, 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());
}
}
}

View File

@ -45,6 +45,11 @@ public class IndexedMessageCodec
return (MessageHandler<MSG>) types.get(msgToReply.getClass());
}
@SuppressWarnings("unchecked")
<MSG> MessageHandler<MSG> findIndex(final short i) {
return (MessageHandler<MSG>) indicies.get(i);
}
@SuppressWarnings("OptionalUsedAsFieldOrParameterType")
class MessageHandler<MSG>
{
@ -85,6 +90,15 @@ public class IndexedMessageCodec
public Optional<Function<MSG, Integer>> 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 <M> void tryDecode(PacketBuffer payload, Supplier<NetworkEvent.Context> context, int payloadIndex, MessageHandler<M> codec)

View File

@ -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<Supplier<? extends List<? extends Pair<String,?>>>> 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 <MSG> void reply(MSG msgToReply, NetworkEvent.Context context)
{
sendTo(msgToReply, context.getNetworkManager(), context.getDirection().reply());
context.getPacketDispatcher().sendPacket(instance.getChannelName(), toBuffer(msgToReply).getLeft());
}
public <M> MessageBuilder<M> messageBuilder(final Class<M> type, int id) {
@ -93,6 +105,7 @@ public class SimpleChannel
private BiConsumer<MSG, Supplier<NetworkEvent.Context>> consumer;
private Function<MSG, Integer> loginIndexGetter;
private BiConsumer<MSG, Integer> loginIndexSetter;
private Supplier<List<Pair<String,MSG>>> loginPacketGenerators;
private static <MSG> MessageBuilder<MSG> forType(final SimpleChannel channel, final Class<MSG> type, int id) {
MessageBuilder<MSG> builder = new MessageBuilder<>();
@ -117,6 +130,24 @@ public class SimpleChannel
this.loginIndexSetter = loginIndexSetter;
return this;
}
public MessageBuilder<MSG> buildLoginPacketList(Supplier<List<Pair<String,MSG>>> loginPacketGenerators) {
this.loginPacketGenerators = loginPacketGenerators;
return this;
}
public MessageBuilder<MSG> 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<MSG> consumer(BiConsumer<MSG, Supplier<NetworkEvent.Context>> 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);
}
}
}
}

View File

@ -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<T, U, V>
{
/**
* Bind arguments to the three consumer to generate a consumer.
*
* <pre>
* {@code
* ThreeConsumer.bindArgs(MyClass::instanceMethodReference, arg1, arg2).apply(myClassInstance)
* }
* </pre>
*
* @return a Consumer which has the second and third arguments bound.
*/
static <T, U, V> Consumer<T> 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);
}

View File

@ -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<Pair<String, FMLHandshakeMessages.S2CRegistry>> 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<NetworkEvent.Context> contextSupplier) {
LOGGER.debug("Received registry packet");
}
}