package net.minecraftforge.fml.common.network.handshake;
import java.util.HashSet;
import java.util.List;
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.common.registry.GameData;
import net.minecraftforge.fml.relauncher.Side;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
/**
* 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 FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.clientListenForServerHandshake();
return HELLO;
}
},
HELLO
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
// write our custom packet registration, always
ctx.writeAndFlush(FMLHandshakeMessage.makeCustomChannelRegistration(NetworkRegistry.INSTANCE.channelNamesFor(Side.CLIENT)));
if (msg == null)
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.abortClientHandshake("VANILLA");
// VANILLA login
return DONE;
}
ServerHello serverHelloPacket = (FMLHandshakeMessage.ServerHello)msg;
FMLLog.info("Server protocol version %x", 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);
return WAITINGSERVERDATA;
}
},
WAITINGSERVERDATA
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
String result = FMLNetworkHandler.checkModList((FMLHandshakeMessage.ModList) msg, Side.SERVER);
if (result != null)
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.rejectHandshake(result);
return ERROR;
}
ctx.writeAndFlush(new FMLHandshakeMessage.HandshakeAck(ordinal())).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
if (!ctx.channel().attr(NetworkDispatcher.IS_LOCAL).get())
{
return WAITINGSERVERCOMPLETE;
}
else
{
return PENDINGCOMPLETE;
}
}
},
WAITINGSERVERCOMPLETE
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
FMLHandshakeMessage.RegistryData pkt = (FMLHandshakeMessage.RegistryData)msg;
GameData.GameDataSnapshot snap = ctx.channel().attr(NetworkDispatcher.FML_GAMEDATA_SNAPSHOT).get();
if (snap == null)
{
snap = new GameData.GameDataSnapshot();
ctx.channel().attr(NetworkDispatcher.FML_GAMEDATA_SNAPSHOT).set(snap);
}
GameData.GameDataSnapshot.Entry entry = new GameData.GameDataSnapshot.Entry();
entry.ids.putAll(pkt.getIdMap());
entry.substitutions.addAll(pkt.getSubstitutions());
snap.entries.put(pkt.getName(), entry);
if (pkt.hasMore())
{
FMLLog.fine("Received Mod Registry mapping for %s: %d IDs %d subs", pkt.getName(), entry.ids.size(), entry.substitutions.size());
return WAITINGSERVERCOMPLETE;
}
ctx.channel().attr(NetworkDispatcher.FML_GAMEDATA_SNAPSHOT).remove();
List<String> locallyMissing = GameData.injectSnapshot(snap, false, false);
if (!locallyMissing.isEmpty())
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.rejectHandshake("Fatally missing blocks and items");
FMLLog.severe("Failed to connect to server: there are %d missing blocks and items", locallyMissing.size());
FMLLog.fine("Missing list: %s", locallyMissing);
return ERROR;
}
ctx.writeAndFlush(new FMLHandshakeMessage.HandshakeAck(ordinal())).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
return PENDINGCOMPLETE;
}
},
PENDINGCOMPLETE
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
ctx.writeAndFlush(new FMLHandshakeMessage.HandshakeAck(ordinal())).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
return COMPLETE;
}
},
COMPLETE
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
NetworkDispatcher dispatcher = ctx.channel().attr(NetworkDispatcher.FML_DISPATCHER).get();
dispatcher.completeClientHandshake();
FMLMessage.CompleteHandshake complete = new FMLMessage.CompleteHandshake(Side.CLIENT);
ctx.fireChannelRead(complete);
ctx.writeAndFlush(new FMLHandshakeMessage.HandshakeAck(ordinal())).addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE);
return DONE;
}
},
DONE
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
if (msg instanceof FMLHandshakeMessage.HandshakeReset)
{
GameData.revertToFrozen();
return HELLO;
}
return this;
}
},
ERROR
{
@Override
public FMLHandshakeClientState accept(ChannelHandlerContext ctx, FMLHandshakeMessage msg)
{
return this;
}
};
}