Compare commits

...

16 Commits

Author SHA1 Message Date
Adrian Bergqvist
fbf12f90e7
Update README.md 2024-06-07 00:24:44 +02:00
Adrian Bergqvist
b2a9eeffa1
1 small extra fix 2024-02-28 20:40:50 +01:00
Icosider
eaa974593c
Fix loading config
Refactoring AmbassadorConfig
2024-02-27 15:30:42 +03:00
Adrian Bergqvist
0fcf6af72f
Fixed config 2024-02-17 18:40:28 +01:00
Adrian Bergqvist
c7c2e1cd33
Implemented kick-reset again with config option 2024-02-17 18:11:04 +01:00
Adrian Bergqvist
9c824abd45
1.18 might not send datapack registries fix 2024-02-15 23:00:24 +01:00
Adrian Bergqvist
25c6152087
Bump to avoid confusion 2024-02-15 20:51:28 +01:00
Adrian Bergqvist
e6532b4e9d
Bump 2024-02-13 02:00:22 +01:00
Adrian Bergqvist
45e41a5b83
zeta compatibility fixed 2024-02-13 01:59:51 +01:00
Adrian Bergqvist
b91d767163
Fixed codec 2024-02-13 01:26:33 +01:00
Adrian Bergqvist
5ffea5bc96
Fixed a small mistake 2024-02-12 14:11:37 +01:00
Adrian Bergqvist
3960efdba5
Bump 2024-02-12 12:33:24 +01:00
Adrian Bergqvist
246b8131b3
Also changed the CRPM complete decoder 2024-02-12 12:27:33 +01:00
Adrian Bergqvist
4b838990a6
Added handler for third party registry messages 2024-02-12 12:18:41 +01:00
Adrian Bergqvist
89372a4a3c
Update README.md 2024-02-01 17:02:34 +01:00
Adrian Bergqvist
34ef23593d
Update README.md 2024-02-01 16:59:02 +01:00
15 changed files with 350 additions and 184 deletions

View File

@ -3,7 +3,8 @@
This is a Velocity plugin that makes it possible to host a modern Forge server behind a Velocity proxy!
Unlike other solutions, this plugin does not require any special modifications to the backend server nor the client. (The player doesn't need to do anything)
## Only for 1.13-1.20.1
Velocity has now added built-in support for newer mc versions and therefore don't need Ambassador. You might still need PCF mod on the server-side, for more info please visit: https://github.com/adde0109/Proxy-Compatible-Forge
## How to get started:
1. Download and install this plugin to your proxy.
2. After starting the server, configure the plugin it to your liking using the config file found in the folder "Ambassador".
@ -13,12 +14,13 @@ Unlike other solutions, this plugin does not require any special modifications t
- https://github.com/caunt/BungeeForge (Legacy forwarding)
## Features
* Server switching using kick to reset the client with configureble message and switch timeout.
* ServerRedirect support for auto-reconnecting during switch.
* Server switching using client mod for instant server switching:
https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
1.18.2 and 1.19 fork:
https://github.com/adde0109/Forge-Client-Reset-Packet
* Server switching without any client side mod when the servers are similar. (Mods must match)
* ServerRedirect support for server switching.
* Server switching using Client Reset Packet Mod for instant server switching:
1.16.5: https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
1.18.2+: https://www.curseforge.com/minecraft/mc-mods/forge-client-reset-packet-forward
## Stuck on "Negotiating":
This is an issue with Client Reset Packet Mod being partly incompatible with certain mods on the client. Please remove incompatible mods on the client if you have this issue. (Yes, this also includes client-side only mods.)

View File

@ -5,7 +5,7 @@ plugins {
}
group = "org.adde0109"
version = "1.5.0-rc1"
version = "1.5.3-beta"
repositories {
mavenCentral()

View File

@ -19,6 +19,7 @@ import java.util.Map;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
@ -28,6 +29,8 @@ import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySeria
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityEventHandler;
@ -43,7 +46,7 @@ import java.util.concurrent.TimeUnit;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
@Plugin(id = "ambassador", name = "Ambassador", version = "1.5.0-rc1", authors = {"adde0109"})
@Plugin(id = "ambassador", name = "Ambassador", version = "1.5.3-beta", authors = {"adde0109"})
public class Ambassador {
//Don't forget to update checkCompatibleVersion() when changing this value
@ -153,6 +156,16 @@ public class Ambassador {
return TEMPORARY_FORCED;
}
public void reconnectSwitchPlayer(ConnectedPlayer player) {
TEMPORARY_FORCED.put(player.getUsername(), player.getConnectionInFlight().getServer(),
config.getServerSwitchCancellationTime(), TimeUnit.SECONDS);
MiniMessage mm = MiniMessage.miniMessage();
Component parsed = mm.deserialize(config.getKickReconnectMessageString());
player.disconnect(parsed);
}
private void initMetrics() {
Metrics metrics = metricsFactory.make(this, 15655);
}

View File

@ -19,7 +19,6 @@ public class AmbassadorConfig {
@Expose
private boolean silenceWarnings = false;
@Expose
private boolean bypassRegistryCheck = false;
@Expose
@ -28,12 +27,21 @@ public class AmbassadorConfig {
@Expose
private boolean debugMode = false;
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck, boolean debugMode) {
@Expose
private boolean enableKickReset = false;
@Expose
private String kickReconnectMessageString = "<red>Please reconnect.</red>";
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck,
boolean debugMode, boolean enableKickReset, String kickReconnectMessageString) {
this.silenceWarnings = silenceWarnings;
this.bypassRegistryCheck = bypassRegistryCheck;
this.bypassModCheck = bypassModCheck;
this.debugMode = debugMode;
};
this.enableKickReset = enableKickReset;
this.kickReconnectMessageString = kickReconnectMessageString;
}
public static AmbassadorConfig read(Path path) throws IOException {
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
@ -59,8 +67,19 @@ public class AmbassadorConfig {
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
boolean debugMode = config.getOrElse("debug-mode", false);
String kickReconnectMessageString = config.getOrElse("disconnect-reset-message",
config.getOrElse("reconnect-message", "<red>Please reconnect.</red>"));
//Upgrade config
if (configVersion <= 1.2) {
if (configVersion <= 2.0) {
Files.delete(path);
config = CommentedFileConfig.builder(path)
.defaultData(defaultConfigLocation)
@ -70,17 +89,17 @@ public class AmbassadorConfig {
.build();
config.load();
config.set("silence-warnings", silenceWarnings);
config.set("serverRedirectTimeout", serverSwitchCancellationTime);
config.set("bypass-registry-checks", bypassRegistryCheck);
config.set("bypass-mod-checks", bypassModCheck);
config.set("debug-mode", debugMode);
config.set("reconnect-message", kickReconnectMessageString);
}
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
boolean enableKickReset = config.getOrElse("enable-kick-reset", false);
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
boolean debugMode = config.getOrElse("debug-mode", false);
return new AmbassadorConfig(bypassRegistryCheck, bypassModCheck, silenceWarnings, debugMode);
return new AmbassadorConfig(silenceWarnings, bypassRegistryCheck, bypassModCheck,
debugMode, enableKickReset, kickReconnectMessageString);
}
public int getServerSwitchCancellationTime() {
@ -102,4 +121,12 @@ public class AmbassadorConfig {
public boolean isDebugMode() {
return debugMode;
}
public boolean isEnableKickReset() {
return enableKickReset;
}
public String getKickReconnectMessageString() {
return kickReconnectMessageString;
}
}

View File

@ -1,5 +1,7 @@
package org.adde0109.ambassador.forge;
import org.adde0109.ambassador.forge.packet.Context;
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
import org.adde0109.ambassador.forge.packet.RegistryPacket;
@ -11,6 +13,7 @@ import java.util.zip.Checksum;
public class ForgeHandshake {
private ModListReplyPacket modListReplyPacket;
private final Map<String, Long> registries = new HashMap<>();
public GenericForgeLoginWrapperPacket<Context.ClientContext> zetaFlagsPacket;
public ForgeHandshake() {
}

View File

@ -6,8 +6,8 @@ import com.google.common.io.ByteStreams;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.handler.codec.DecoderException;
import org.adde0109.ambassador.forge.packet.Context;
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
import java.nio.charset.StandardCharsets;
@ -117,30 +117,52 @@ public class ForgeHandshakeUtils {
return stream.toByteArray();
}
public static class SilentGearUtils {
public static boolean isSilentGearPacket(byte[] data) {
ByteBuf buf = Unpooled.wrappedBuffer(data);
String channel = null;
try {
channel = ProtocolUtils.readString(buf);
} catch (DecoderException e) {
} finally {
buf.release();
}
return channel != null && channel.equals("silentgear:network");
}
public static class ThirdPartyRegistryUtils {
public static class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
private final Context.ClientContext context;
public ACKPacket(Context.ClientContext context) {
this.context = context;
}
static enum ThirdPartyChannel {
SILENTGEAR_NETWORK {
@Override
public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed) {
return new ACKPacket(context, 3);
}
},
ZETA_MAIN {
@Override
public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed) {
return new GenericForgeLoginWrapperPacket<Context.ClientContext>(completed.zetaFlagsPacket.getContent(), context);
}
};
abstract public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed);
}
static boolean isThirdPartyPacket(GenericForgeLoginWrapperPacket<Context> packet) {
try {
Enum.valueOf(ThirdPartyChannel.class,
packet.getContext().getChannelName().replace(':', '_').toUpperCase());
return true;
} catch (IllegalArgumentException e) {
return false;
}
}
static ThirdPartyChannel getThirdPartyChannel(GenericForgeLoginWrapperPacket<Context> packet) {
return Enum.valueOf(ThirdPartyChannel.class,
packet.getContext().getChannelName().replace(':', '_').toUpperCase());
}
static class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
private final Context.ClientContext context;
private final int packetID;
ACKPacket(Context.ClientContext context, int packetID) {
this.context = context;
this.packetID = packetID;
}
public ByteBuf encode() {
ByteBuf buf = Unpooled.buffer();
ProtocolUtils.writeVarInt(buf, 3);
ProtocolUtils.writeVarInt(buf, packetID);
return buf;
}

View File

@ -62,7 +62,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
VelocityForgeBackendConnectionPhase() {
}
public void handle(VelocityServerConnection server, ConnectedPlayer player, IForgeLoginWrapperPacket message) {
public void handle(VelocityServerConnection server, ConnectedPlayer player, IForgeLoginWrapperPacket<Context> message) {
VelocityForgeBackendConnectionPhase newPhase = getNewPhase(server,message);
server.setConnectionPhase(newPhase);
@ -72,6 +72,10 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
if (!player.isActive()) {
return;
}
if (!clientPhase.consideredComplete()) {
//Initial Forge
if (message instanceof ModListPacket modListPacket) {
@ -92,10 +96,16 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
if (clientPhase.forgeHandshake.getModListReplyPacket() == null) {
//We have nothing to respond with during this handshake. Unable to proceed.
Ambassador.getInstance().logger.error("Unable for {} to switch servers. " +
"Vanilla({}) -> Forge({}) switch without reset is not yet supported!", player.getGameProfile().getName(),
player.getConnectedServer().getServerInfo().getName(), server.getServerInfo().getName());
if (Ambassador.getInstance().config.isEnableKickReset()) {
//Kick-reset
Ambassador.getInstance().reconnectSwitchPlayer(player);
} else {
Ambassador.getInstance().logger.error("Unable for {} to switch servers. Vanilla({}) -> Forge({}) switch " +
"without client side mod or kick-reset enabled is not yet supported!",
player.getGameProfile().getName(), player.getConnectedServer().getServerInfo().getName(),
server.getServerInfo().getName());
server.disconnect();
}
return;
}
@ -124,6 +134,9 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
clientPhase.forgeHandshake.isCompatible(handshake)) {
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
} else if (Ambassador.getInstance().config.isEnableKickReset()) {
//Kick-reset
Ambassador.getInstance().reconnectSwitchPlayer(player);
} else {
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
server.getServer().getServerInfo().getName() + " being different from the registries of " +
@ -137,10 +150,13 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
remainingRegistries.countDown();
} else if (message instanceof ConfigDataPacket) {
server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
} else if (message instanceof GenericForgeLoginWrapperPacket<?> packet
&& ForgeHandshakeUtils.SilentGearUtils.isSilentGearPacket(packet.getContent())) {
server.getConnection().write(new ForgeHandshakeUtils.SilentGearUtils.ACKPacket(
Context.fromContext(message.getContext(), true)));
} else if (message instanceof GenericForgeLoginWrapperPacket<Context> packet
&& ForgeHandshakeUtils.ThirdPartyRegistryUtils.isThirdPartyPacket(packet)) {
server.getConnection().write(
ForgeHandshakeUtils.ThirdPartyRegistryUtils.getThirdPartyChannel(packet).
generateResponsePacket(
Context.ClientContext.fromContext(packet.getContext(), true),
clientPhase.forgeHandshake));
}
}
//Forge server

View File

@ -17,6 +17,7 @@ import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.packet.Context;
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
@ -24,6 +25,7 @@ import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit;
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
@ -129,6 +131,10 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket<Context.ClientContext> msg, VelocityServerConnection server) {
if (msg.getContext().getChannelName().equals("zeta:main")) {
forgeHandshake.zetaFlagsPacket = (GenericForgeLoginWrapperPacket<Context.ClientContext>) msg;
}
if (msg instanceof ModListReplyPacket replyPacket) {
ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map(
(v) -> new ModInfo.Mod(v,"1")).toList());
@ -250,6 +256,8 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
player.getConnection().write(new PluginMessagePacket("srvredirect:red", buf));
Ambassador.getInstance().reconnectSwitchPlayer(player);
}
};

View File

@ -4,31 +4,38 @@ public class Context {
private final int responseID;
private Context(int responseID) {
private final String channelName;
private Context(int responseID, String channelName) {
this.responseID = responseID;
this.channelName = channelName;
}
public static Context createContext(int responseID) {
return new Context(responseID);
public static Context createContext(int responseID, String channelName) {
return new Context(responseID, channelName);
}
public static ClientContext createClientContext(int responseID, boolean clientSuccess) {
return new ClientContext(responseID,clientSuccess);
public static ClientContext createClientContext(int responseID, boolean clientSuccess, String channelName) {
return new ClientContext(responseID, clientSuccess, channelName);
}
public static ClientContext fromContext(Context context, boolean clientSuccess) {
return new ClientContext(context.responseID,clientSuccess);
return new ClientContext(context.responseID, clientSuccess, context.channelName);
}
public int getResponseID() {
return responseID;
}
public String getChannelName() {
return channelName;
}
public static class ClientContext extends Context {
private final boolean clientSuccess;
ClientContext(int responseID, boolean clientSuccess) {
super(responseID);
ClientContext(int responseID, boolean clientSuccess, String channelName) {
super(responseID, channelName);
this.clientSuccess = clientSuccess;
}

View File

@ -8,7 +8,7 @@ public class GenericForgeLoginWrapperPacket<T extends Context> implements IForge
private final byte[] content;
private final T context;
GenericForgeLoginWrapperPacket(byte[] content, T context) {
public GenericForgeLoginWrapperPacket(byte[] content, T context) {
this.content = content;
this.context = context;
}

View File

@ -1,10 +1,39 @@
package org.adde0109.ambassador.forge.packet;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
public class ModDataPacket extends GenericForgeLoginWrapperPacket<Context> {
public class ModDataPacket implements IForgeLoginWrapperPacket<Context> {
private final byte[] content;
private final Context context;
ModDataPacket(byte[] content, Context context) {
super(content, context);
this.content = content;
this.context = context;
}
static public ModDataPacket read(ByteBuf input, Context context) {
byte[] content = new byte[input.readableBytes()];
input.readBytes(content);
return new ModDataPacket(content, context);
}
@Override
public ByteBuf encode() {
ByteBuf buf = Unpooled.buffer();
ProtocolUtils.writeVarInt(buf, 5); //PacketID
buf.writeBytes(content);
return buf;
}
public byte[] getContent() {
return content;
}
@Override
public Context getContext() {
return context;
}
}

View File

@ -46,7 +46,7 @@ public class ModListPacket implements IForgeLoginWrapperPacket<Context> {
registries.add(ProtocolUtils.readString(input, 32767));
List<String> dataPackRegistries = null;
if (FML3) {
if (FML3 && input.isReadable()) {
dataPackRegistries = new ArrayList<>();
len = ProtocolUtils.readVarInt(input);
for (int x = 0; x < len; x++)

View File

@ -9,40 +9,62 @@ import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.MessageToMessageCodec;
import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
import io.netty.util.ReferenceCountUtil;
import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.packet.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> {
private final boolean FML3;
private final List<Integer> loginWrapperIDs = new ArrayList<>();
private final Map<Integer, Context> loginWrapperContexts = new HashMap<>();
public ForgeLoginWrapperCodec(boolean fml3) {
FML3 = fml3;
}
@Override
public boolean acceptInboundMessage(Object msg) throws Exception {
return (msg instanceof LoginPluginMessagePacket
&& ((LoginPluginMessagePacket) msg).getChannel().equals("fml:loginwrapper"))
|| (msg instanceof LoginPluginResponsePacket
&& loginWrapperContexts.containsKey(((LoginPluginResponsePacket) msg).getId()));
}
@Override
protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception {
ByteBuf buf = in.content();
Context context;
if (in instanceof LoginPluginMessagePacket msg && msg.getChannel().equals("fml:loginwrapper")) {
context = Context.createContext(msg.getId());
} else if (in instanceof LoginPluginResponsePacket msg && loginWrapperIDs.remove(Integer.valueOf(msg.getId()))) {
context = Context.createClientContext(msg.getId(), msg.isSuccess());
} else {
ctx.fireChannelRead(in.retain());
if (in instanceof LoginPluginResponsePacket msg) {
//Continue from stored context
context = Context.fromContext(
loginWrapperContexts.remove(((LoginPluginResponsePacket) msg).getId()), msg.isSuccess());
if (!msg.isSuccess()) {
//Nothing to read, just create an empty packet.
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
return;
} else {
String channel = ProtocolUtils.readString(buf); //Read the channel even though we know the channel by context.
Ambassador.getInstance();
}
} else {
//New context.
LoginPluginMessagePacket msg = (LoginPluginMessagePacket) in;
String channel = ProtocolUtils.readString(buf);
context = Context.createContext(msg.getId(), channel);
}
//Decoding of data starts here - channel already read
int originalReaderIndex = buf.readerIndex();
try {
String channel = ProtocolUtils.readString(buf);
if (!channel.equals("fml:handshake")) {
throw new DecoderException();
if (!context.getChannelName().equals("fml:handshake")) {
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
} else {
int length = ProtocolUtils.readVarInt(buf);
int packetID = ProtocolUtils.readVarInt(buf);
@ -55,7 +77,13 @@ public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBu
out.add(new ACKPacket(clientContext));
break;
default:
throw new DecoderException();
//Undo decoding
buf.readerIndex(originalReaderIndex);
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
if (Ambassador.getInstance().config.isDebugMode()) {
Ambassador.getInstance().logger.warn(
"Unrecognised packet id received from client on fml:handshake: " + packetID);
}
}
} else {
switch (packetID) {
@ -70,45 +98,48 @@ public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBu
break;
case 5:
if (FML3) {
buf.readerIndex(originalReaderIndex);
out.add(ModDataPacket.read(buf, context));
break;
}
default:
throw new DecoderException();
}
}
}
} catch (DecoderException e) {
//Undo decoding
buf.readerIndex(originalReaderIndex);
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
if (Ambassador.getInstance().config.isDebugMode()) {
Ambassador.getInstance().logger.warn(
"Unrecognised packet id received from server on fml:handshake: " + packetID);
}
}
}
}
}
@Override
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
ByteBuf wrapped;
if (msg instanceof GenericForgeLoginWrapperPacket<?>) {
wrapped = msg.encode();
} else {
String channel = "fml:handshake";
if (msg instanceof ForgeHandshakeUtils.SilentGearUtils.ACKPacket) {
channel = "silentgear:network";
}
boolean data = !(msg.getContext() instanceof Context.ClientContext clientContext && !clientContext.success());
boolean includeLength = !(msg instanceof GenericForgeLoginWrapperPacket);
String channel = msg.getContext().getChannelName();
wrapped = Unpooled.buffer();
if (data) {
ByteBuf encoded = msg.encode();
ProtocolUtils.writeString(wrapped, channel);
if (includeLength)
ProtocolUtils.writeVarInt(wrapped, encoded.readableBytes());
wrapped.writeBytes(encoded);
encoded.release();
}
if (msg.getContext() instanceof Context.ClientContext clientContext) {
out.add(new LoginPluginResponsePacket(clientContext.getResponseID(), clientContext.success(), wrapped));
} else {
out.add(new LoginPluginMessagePacket(msg.getContext().getResponseID(), "fml:loginwrapper", wrapped));
if (!(msg instanceof ModDataPacket)) {
this.loginWrapperIDs.add(msg.getContext().getResponseID());
if (!(msg instanceof ModDataPacket)) { //ModDataPacket doesn't require a response
this.loginWrapperContexts.put(msg.getContext().getResponseID(), msg.getContext());
}
}
}

View File

@ -28,7 +28,7 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter {
if (id == 98) {
try {
ctx.fireChannelRead(GenericForgeLoginWrapperPacket.read(
Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success)));
Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success, "fml:handshake")));
} finally {
buf.release();
}

View File

@ -1,5 +1,5 @@
# Do not change this
config-version = "2.0"
config-version = "2.1"
# How much time the player has to reconnect before canceling the server switch. (In seconds)
# Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
@ -19,3 +19,11 @@ bypass-mod-checks = false
# Only for debug/troubleshooting
debug-mode = false
# Make the player reconnect when server switching as a last resort if
# the player can't reset or switch servers without resetting.
enable-kick-reset = false
# Message to display when player gets kicked due to server switching according to enable-kick-reset.
# This input is deserialzied as MiniMessage and support formating according to the MiniMessage format.
reconnect-message = "<red>Please reconnect.</red>"