Velocity
Velocity copied to clipboard
Track registered PluginMessageChannels from clientside
Base Environment
Minecraft Forge 1.12.2
1 Velocity Serer
Multi paper backend servers
Introduction
- Bukkit Server cannot send any message to client after server switching.
- PlayerRegisterChannelEvent not called after server switching
Mininal Reproduction
- Write one mod that reciving custom message and only System.out.println it
- Write one bukkit plugin that register plugin message with one command to send plugin message
- Startup 2 1.12.2 paper server
- Login to velocity, and execute
/server myserver2
- Execute the
/myplugin:sendpluginmessage
, nothing printed in minecraft client
Analyzing
FML 1.12.2 will register and only register mod outgoing channels durning connection initialization. When server switching happen, Forge will not send registered channels again. Velocity need to handle it and send it to backend manually.
/*
* Minecraft Forge
* Copyright (c) 2016-2020.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation version 2.1
* of the License.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package net.minecraftforge.fml.common.network.handshake;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import java.util.Map;
import java.util.function.Consumer;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.google.common.util.concurrent.Futures;
import net.minecraft.client.Minecraft;
import net.minecraft.util.ResourceLocation;
import net.minecraftforge.fml.common.FMLLog;
import net.minecraftforge.fml.common.Loader;
import net.minecraftforge.fml.common.network.NetworkRegistry;
import net.minecraftforge.fml.common.network.handshake.FMLHandshakeMessage.ServerHello;
import net.minecraftforge.fml.common.network.internal.FMLMessage;
import net.minecraftforge.fml.common.network.internal.FMLNetworkHandler;
import net.minecraftforge.fml.relauncher.Side;
import net.minecraftforge.registries.ForgeRegistry;
import net.minecraftforge.registries.GameData;
/**
* Packet handshake sequence manager- client side (responding to remote server)
*
* Flow:
* 1. Wait for server hello. (START). Move to HELLO state.
* 2. Receive Server Hello. Send customchannel registration. Send Client Hello. Send our modlist. Move to WAITINGFORSERVERDATA state.
* 3. Receive server modlist. Send ack if acceptable, else send nack and exit error. Receive server IDs. Move to COMPLETE state. Send ack.
*
* @author cpw
*
*/
enum FMLHandshakeClientState implements IHandshakeState<FMLHandshakeClientState>
{
START
{
@Override
public void accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg, Consumer<? super FMLHandshakeClientState> cons)
{
cons.accept(HELLO);
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.clientListenForServerHandshake();
}
},
HELLO
{
@Override
public void accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg, Consumer<? super FMLHandshakeClientState> cons)
{
boolean isVanilla = msg == null;
if (isVanilla)
{
cons.accept(DONE);
}
else
{
cons.accept(WAITINGSERVERDATA);
}
/////////////////////////////////////////////
// HERE
/////////////////////////////////////////////
// write our custom packet registration, always
ctx.writeAndFlush(FMLHandshakeMessage.makeCustomChannelRegistration(NetworkRegistry.INSTANCE.channelNamesFor(Side.CLIENT)));
if (isVanilla)
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.abortClientHandshake("VANILLA");
// VANILLA login
return;
}
ServerHello serverHelloPacket = (FMLHandshakeMessage.ServerHello)msg;
FMLLog.log.info("Server protocol version {}", Integer.toHexString(serverHelloPacket.protocolVersion()));
if (serverHelloPacket.protocolVersion() > 1)
{
// Server sent us an extra dimension for the logging in player - stash it for retrieval later
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.setOverrideDimension(serverHelloPacket.overrideDim());
}
ctx.writeAndFlush(new FMLHandshakeMessage.ClientHello()).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
ctx.writeAndFlush(new FMLHandshakeMessage.ModList(Loader.instance().getActiveModList())).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
}
},
Addition
- PlayerRegsiterChannelEvent is triggered by
REGISTER
message from player. -
sendPluginMessage
is require channel was registered by player.
CraftPlayer.java
@Override
public void sendPluginMessage(Plugin source, String channel, byte[] message) {
StandardMessenger.validatePluginMessage(server.getMessenger(), source, channel, message);
if (getHandle().playerConnection == null) return;
if (channels.contains(channel)) {
PacketPlayOutCustomPayload packet = new PacketPlayOutCustomPayload(channel, new PacketDataSerializer(Unpooled.wrappedBuffer(message)));
getHandle().playerConnection.sendPacket(packet);
}
}
public void addChannel(String channel) {
com.google.common.base.Preconditions.checkState(DISABLE_CHANNEL_LIMIT || channels.size() < 128, "Too many channels registered"); // Spigot // Paper - flag to disable channel limit
if (channels.add(channel)) {
server.getPluginManager().callEvent(new PlayerRegisterChannelEvent(this, channel));
}
}
Releated
https://github.com/PaperMC/Velocity/pull/591 https://github.com/PaperMC/Velocity/issues/1068