Compare commits

..

24 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
Adrian Bergqvist
8767d55873
Updated minVelocityVersion 2024-02-01 16:15:00 +01:00
Adrian Bergqvist
cd6c0be56e
Prevent Vanilla -> Forge switching with error message 2024-02-01 16:04:29 +01:00
Adrian Bergqvist
6dfbdecfaf
Recoded client phases and fixed FORGE - FORGE switch 2024-02-01 15:47:00 +01:00
Adrian Bergqvist
550328863a
Fixed reset mechanism 2024-01-22 23:57:27 +01:00
Adrian Bergqvist
7af0f5c880
Velocity refactor 2024-01-22 19:39:33 +01:00
Adrian Bergqvist
222b22eb3c
WIP handshake mechanism rewrite 2024-01-22 19:32:28 +01:00
Adrian Bergqvist
3fa30b7362
Debug mode 2023-12-29 01:40:32 +01:00
Adrian Bergqvist
cafa8adffd
New config. Able to bypass checks 2023-12-20 19:09:09 +01:00
25 changed files with 655 additions and 364 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! 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) 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: ## How to get started:
1. Download and install this plugin to your proxy. 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". 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) - https://github.com/caunt/BungeeForge (Legacy forwarding)
## Features ## Features
* Server switching using kick to reset the client with configureble message and switch timeout. * Server switching without any client side mod when the servers are similar. (Mods must match)
* ServerRedirect support for auto-reconnecting during switch. * ServerRedirect support for server switching.
* Server switching using client mod for instant server switching: * Server switching using Client Reset Packet Mod for instant server switching:
https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
1.18.2 and 1.19 fork: 1.16.5: https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
https://github.com/adde0109/Forge-Client-Reset-Packet
1.18.2+: https://www.curseforge.com/minecraft/mc-mods/forge-client-reset-packet-forward
## Stuck on "Negotiating": ## 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.) 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.)

@ -1 +1 @@
Subproject commit eb594fc799281ff418dc2c161c2d8a8eb0c89a19 Subproject commit c3583e182ca6585e40d1eef0da8c18547c0b1bc1

View File

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

View File

@ -19,14 +19,18 @@ import java.util.Map;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.ConnectionManager; import com.velocitypowered.proxy.network.ConnectionManager;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier;
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry;
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer; import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer; 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.VelocityBackendChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer; import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
import org.adde0109.ambassador.velocity.VelocityEventHandler; import org.adde0109.ambassador.velocity.VelocityEventHandler;
@ -42,11 +46,11 @@ import java.util.concurrent.TimeUnit;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19; import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet; import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
@Plugin(id = "ambassador", name = "Ambassador", version = "1.4.3-beta", authors = {"adde0109"}) @Plugin(id = "ambassador", name = "Ambassador", version = "1.5.3-beta", authors = {"adde0109"})
public class Ambassador { public class Ambassador {
//Don't forget to update checkCompatibleVersion() when changing this value //Don't forget to update checkCompatibleVersion() when changing this value
private static final String minVelocityVersion = "velocity-3.2.0-SNAPSHOT-266"; private static final String minVelocityVersion = "velocity-3.3.0-SNAPSHOT-330";
public ProxyServer server; public ProxyServer server;
public final Logger logger; public final Logger logger;
@ -75,11 +79,11 @@ public class Ambassador {
boolean checkCompatibleVersion() { boolean checkCompatibleVersion() {
//Update this when changing minVelocityVersion //Update this when changing minVelocityVersion
try { try {
MinecraftConnection.class.getDeclaredMethod("setActiveSessionHandler", StateRegistry.class); Class.forName("com.velocitypowered.proxy.protocol.packet.DisconnectPacket");
} catch (NoSuchMethodException e) { } catch (ClassNotFoundException e) {
return false; throw new RuntimeException(e);
} }
return true; return true;
} }
@Subscribe(order = PostOrder.LAST) @Subscribe(order = PostOrder.LAST)
@ -96,7 +100,6 @@ public class Ambassador {
Path configPath = dataDirectory.resolve("Ambassador.toml"); Path configPath = dataDirectory.resolve("Ambassador.toml");
config = AmbassadorConfig.read(configPath); config = AmbassadorConfig.read(configPath);
config.validate();
inject(); inject();
@ -111,7 +114,6 @@ public class Ambassador {
try { try {
Path configPath = dataDirectory.resolve("Ambassador.toml"); Path configPath = dataDirectory.resolve("Ambassador.toml");
final AmbassadorConfig newconfig = AmbassadorConfig.read(configPath); final AmbassadorConfig newconfig = AmbassadorConfig.read(configPath);
newconfig.validate();
config = newconfig; config = newconfig;
} catch (Exception e) { } catch (Exception e) {
@ -154,6 +156,16 @@ public class Ambassador {
return TEMPORARY_FORCED; 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() { private void initMetrics() {
Metrics metrics = metricsFactory.make(this, 15655); Metrics metrics = metricsFactory.make(this, 15655);
} }

View File

@ -7,86 +7,126 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import java.io.IOException;
import java.net.URL; import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
public class AmbassadorConfig { public class AmbassadorConfig {
@Expose
private int serverSwitchCancellationTime = 30;
@Expose @Expose
private String disconnectResetMessage = "Please reconnect"; private boolean silenceWarnings = false;
@Expose
private boolean bypassRegistryCheck = false;
@Expose
private boolean bypassModCheck = false;
@Expose @Expose
private int serverSwitchCancellationTime = 120; private boolean debugMode = false;
@Expose @Expose
private boolean silenceWarnings = false; private boolean enableKickReset = false;
private net.kyori.adventure.text.@MonotonicNonNull Component messageAsAsComponent; @Expose
private String kickReconnectMessageString = "<red>Please reconnect.</red>";
private AmbassadorConfig(String kickResetMessage, int serverSwitchCancellationTime, boolean silenceWarnings) { private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck,
this.disconnectResetMessage = kickResetMessage; boolean debugMode, boolean enableKickReset, String kickReconnectMessageString) {
this.serverSwitchCancellationTime = serverSwitchCancellationTime; this.silenceWarnings = silenceWarnings;
this.silenceWarnings = silenceWarnings; this.bypassRegistryCheck = bypassRegistryCheck;
}; this.bypassModCheck = bypassModCheck;
this.debugMode = debugMode;
public void validate() { this.enableKickReset = enableKickReset;
if (serverSwitchCancellationTime <= 0) { this.kickReconnectMessageString = kickReconnectMessageString;
throw new InvalidValueException("'server-switch-cancellation-time' can't be less than nor equal to zero: server-switch-cancellation-time=" + serverSwitchCancellationTime);
}
}
public static AmbassadorConfig read(Path path) {
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
.getResource("default-ambassador.toml");
if (defaultConfigLocation == null) {
throw new RuntimeException("Default configuration file does not exist.");
} }
CommentedFileConfig config = CommentedFileConfig.builder(path) public static AmbassadorConfig read(Path path) throws IOException {
.defaultData(defaultConfigLocation) URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
.autosave() .getResource("default-ambassador.toml");
.preserveInsertionOrder() if (defaultConfigLocation == null) {
.sync() throw new RuntimeException("Default configuration file does not exist.");
.build(); }
config.load();
double configVersion; CommentedFileConfig config = CommentedFileConfig.builder(path)
try { .defaultData(defaultConfigLocation)
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0")); .autosave()
} catch (NumberFormatException e) { .preserveInsertionOrder()
configVersion = 1.0; .sync()
.build();
config.load();
double configVersion;
try {
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
} catch (NumberFormatException e) {
configVersion = 1.0;
}
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 <= 2.0) {
Files.delete(path);
config = CommentedFileConfig.builder(path)
.defaultData(defaultConfigLocation)
.autosave()
.preserveInsertionOrder()
.sync()
.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);
}
boolean enableKickReset = config.getOrElse("enable-kick-reset", false);
return new AmbassadorConfig(silenceWarnings, bypassRegistryCheck, bypassModCheck,
debugMode, enableKickReset, kickReconnectMessageString);
} }
if (configVersion < 1.1) { public int getServerSwitchCancellationTime() {
config.set("silence-warnings", false); return serverSwitchCancellationTime;
config.set("config-version", "1.2");
} }
String kickResetMessage = config.getOrElse("disconnect-reset-message", "Please reconnect"); public boolean isSilenceWarnings() {
int serverSwitchCancellationTime = config.getIntOrElse("server-switch-cancellation-time", 120); return silenceWarnings;
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
return new AmbassadorConfig(kickResetMessage, serverSwitchCancellationTime, silenceWarnings);
}
public net.kyori.adventure.text.Component getDisconnectResetMessage() {
if (messageAsAsComponent == null) {
if (disconnectResetMessage.startsWith("{")) {
messageAsAsComponent = GsonComponentSerializer.gson().deserialize(disconnectResetMessage);
} else {
messageAsAsComponent = LegacyComponentSerializer.legacyAmpersand().deserialize(disconnectResetMessage);
}
} }
return messageAsAsComponent;
}
public int getServerSwitchCancellationTime() { public boolean isBypassRegistryCheck() {
return serverSwitchCancellationTime; return bypassRegistryCheck;
} }
public boolean isSilenceWarnings() { public boolean isBypassModCheck() {
return silenceWarnings; return bypassModCheck;
} }
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; 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.ModListReplyPacket;
import org.adde0109.ambassador.forge.packet.RegistryPacket; import org.adde0109.ambassador.forge.packet.RegistryPacket;
@ -11,6 +13,7 @@ import java.util.zip.Checksum;
public class ForgeHandshake { public class ForgeHandshake {
private ModListReplyPacket modListReplyPacket; private ModListReplyPacket modListReplyPacket;
private final Map<String, Long> registries = new HashMap<>(); private final Map<String, Long> registries = new HashMap<>();
public GenericForgeLoginWrapperPacket<Context.ClientContext> zetaFlagsPacket;
public ForgeHandshake() { public ForgeHandshake() {
} }

View File

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

View File

@ -0,0 +1,70 @@
package org.adde0109.ambassador.forge;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import org.adde0109.ambassador.forge.packet.*;
import java.util.HashMap;
import java.util.Map;
import java.util.zip.Adler32;
import java.util.zip.Checksum;
public class ShadowHandshakeReceiver {
private final ConnectedPlayer player;
private final ModListReplyPacket modListReplyPacket;
private final Map<String, Long> registries;
private ShadowHandshakeReceiver(ConnectedPlayer player, ModListReplyPacket modListReplyPacket,
Map<String, Long> registries) {
this.player = player;
this.modListReplyPacket = modListReplyPacket;
this.registries = registries;
}
void handle(IForgeLoginWrapperPacket packet) throws IncompatibleHandshake {
}
void handle(ModListPacket packet) throws IncompatibleHandshake {
//player.getConnection().write();
}
void handle(RegistryPacket packet) throws IncompatibleHandshake {
player.getConnection().write(new ACKPacket(Context.fromContext(packet.getContext(), true)));
}
void handle(ConfigDataPacket packet) throws IncompatibleHandshake {
player.getConnection().write(new ACKPacket(Context.fromContext(packet.getContext(), true)));
}
static class Builder {
ConnectedPlayer player;
ModListReplyPacket modListReplyPacket;
Map<String, Long> registries = new HashMap<>();
void addModListPacket(ModListPacket packet) {
}
void setModListReplyPacket(ModListReplyPacket packet) {
this.modListReplyPacket = packet;
}
void addRegistryPacket(RegistryPacket packet) {
Checksum registryChecksum = new Adler32();
registryChecksum.update(packet.getSnapshot());
registries.put(packet.getRegistryName(), registryChecksum.getValue());
}
ShadowHandshakeReceiver build() {
return new ShadowHandshakeReceiver(player, modListReplyPacket, registries);
}
}
class IncompatibleHandshake extends Throwable {
}
}

View File

@ -6,8 +6,10 @@ import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import net.kyori.adventure.text.Component;
import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.packet.*; import org.adde0109.ambassador.forge.packet.*;
import org.adde0109.ambassador.forge.pipeline.CommandDecoderErrorCatcher; import org.adde0109.ambassador.forge.pipeline.CommandDecoderErrorCatcher;
@ -15,10 +17,10 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhase { public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhase {
NOT_STARTED() { NOT_STARTED {
@Override @Override
VelocityForgeBackendConnectionPhase nextPhase() { VelocityForgeBackendConnectionPhase nextPhase() {
return WAITING_FOR_ACK; return IN_PROGRESS;
} }
@Override @Override
@ -27,7 +29,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
return true; return true;
} }
}, },
WAITING_FOR_ACK() { IN_PROGRESS {
@Override @Override
public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) { public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) {
serverCon.setConnectionPhase(VelocityForgeBackendConnectionPhase.COMPLETE); serverCon.setConnectionPhase(VelocityForgeBackendConnectionPhase.COMPLETE);
@ -47,7 +49,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
} }
}, },
COMPLETE() { COMPLETE {
@Override @Override
public boolean consideredComplete() { public boolean consideredComplete() {
return true; return true;
@ -60,20 +62,22 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
VelocityForgeBackendConnectionPhase() { 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); VelocityForgeBackendConnectionPhase newPhase = getNewPhase(server,message);
server.setConnectionPhase(newPhase); server.setConnectionPhase(newPhase);
//Reset client if not ready to receive new handshake //Forge -> Forge
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase(); VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
if (clientPhase == VelocityForgeClientConnectionPhase.RESETTABLE) {
//Initial Forge
//Forge -> Forge if (!player.isActive()) {
clientPhase.resetConnectionPhase(player); return;
} }
if (clientPhase != VelocityForgeClientConnectionPhase.COMPLETE) { if (!clientPhase.consideredComplete()) {
//Initial Forge
if (message instanceof ModListPacket modListPacket) { if (message instanceof ModListPacket modListPacket) {
clientPhase.forgeHandshake = new ForgeHandshake(); clientPhase.forgeHandshake = new ForgeHandshake();
} }
@ -82,8 +86,37 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
} }
player.getConnection().write(message); player.getConnection().write(message);
} else { } else {
//Reset client if not ready to receive new handshake
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.CRP ||
clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.SR) {
clientPhase.resetConnectionPhase(player);
player.getConnection().write(message);
return;
}
if (clientPhase.forgeHandshake.getModListReplyPacket() == null) {
//We have nothing to respond with during this handshake. Unable to proceed.
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;
}
if (message instanceof ModListPacket modListPacket) { if (message instanceof ModListPacket modListPacket) {
remainingRegistries = new CountDownLatch(modListPacket.getRegistries().size()); remainingRegistries = new CountDownLatch(modListPacket.getRegistries().size());
if (Ambassador.getInstance().config.isDebugMode())
player.sendMessage(Component.text("Expecting " + modListPacket.getRegistries().size() +
" packets from server " + server.getServer().getServerInfo().getName()));
long time = System.currentTimeMillis();
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
try { try {
remainingRegistries.await(); remainingRegistries.await();
@ -91,22 +124,39 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
throw new RuntimeException(e); throw new RuntimeException(e);
} }
}).thenAcceptAsync((v) -> { }).thenAcceptAsync((v) -> {
if (true) {
if(Ambassador.getInstance().config.isDebugMode()) {
player.sendMessage(Component.text("Handshake took: " + (System.currentTimeMillis()-time) + " ms"));
player.sendMessage(Component.text("Avg packet time" +
(System.currentTimeMillis()-time)/modListPacket.getRegistries().size() + " ms"));
}
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
clientPhase.forgeHandshake.isCompatible(handshake)) {
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket()); server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
} else if (Ambassador.getInstance().config.isEnableKickReset()) {
//Kick-reset
Ambassador.getInstance().reconnectSwitchPlayer(player);
} else { } else {
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
server.getServer().getServerInfo().getName() + " being different from the registries of " +
player.getConnectedServer().getServer().getServerInfo().getName());
server.disconnect(); server.disconnect();
} }
}, server.ensureConnected().eventLoop()); }, server.ensureConnected().eventLoop());
} else if (message instanceof RegistryPacket registryPacket) { } else if (message instanceof RegistryPacket registryPacket) {
server.getConnection().write(new ACKPacket(Context.createContext(message.getContext().getResponseID(), true))); server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
handshake.addRegistry(registryPacket); handshake.addRegistry(registryPacket);
remainingRegistries.countDown(); remainingRegistries.countDown();
} else if (message instanceof ConfigDataPacket) { } else if (message instanceof ConfigDataPacket) {
server.getConnection().write(new ACKPacket(Context.createContext(message.getContext().getResponseID(), true))); server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
} else if (message instanceof GenericForgeLoginWrapperPacket<?> packet } else if (message instanceof GenericForgeLoginWrapperPacket<Context> packet
&& ForgeHandshakeUtils.SilentGearUtils.isSilentGearPacket(packet.getContent())) { && ForgeHandshakeUtils.ThirdPartyRegistryUtils.isThirdPartyPacket(packet)) {
server.getConnection().write(new ForgeHandshakeUtils.SilentGearUtils.ACKPacket( server.getConnection().write(
Context.createContext(message.getContext().getResponseID(), true))); ForgeHandshakeUtils.ThirdPartyRegistryUtils.getThirdPartyChannel(packet).
generateResponsePacket(
Context.ClientContext.fromContext(packet.getContext(), true),
clientPhase.forgeHandshake));
} }
} }
//Forge server //Forge server
@ -134,9 +184,9 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
} }
@Override @Override
public boolean handle(VelocityServerConnection server, ConnectedPlayer player, PluginMessage message) { public boolean handle(VelocityServerConnection server, ConnectedPlayer player, PluginMessagePacket message) {
if (message.getChannel().equals("ambassador:commands")) { if (message.getChannel().equals("ambassador:commands")) {
AvailableCommands packet = new AvailableCommands(); AvailableCommandsPacket packet = new AvailableCommandsPacket();
packet.decode(message.content(), ProtocolUtils.Direction.CLIENTBOUND,server.getConnection().getProtocolVersion()); packet.decode(message.content(), ProtocolUtils.Direction.CLIENTBOUND,server.getConnection().getProtocolVersion());
server.getConnection().getActiveSessionHandler().handle(packet); server.getConnection().getActiveSessionHandler().handle(packet);
return true; return true;

View File

@ -10,19 +10,20 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.PluginMessage; import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import net.kyori.adventure.text.Component;
import org.adde0109.ambassador.Ambassador; import org.adde0109.ambassador.Ambassador;
import org.adde0109.ambassador.forge.packet.Context; 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.IForgeLoginWrapperPacket;
import org.adde0109.ambassador.forge.packet.ModListReplyPacket; import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder; import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder; import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
import org.adde0109.ambassador.velocity.client.ClientPacketQueue; import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
import javax.swing.*;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -35,59 +36,15 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
} }
@Override @Override
public void resetConnectionPhase(ConnectedPlayer player) { public void complete(ConnectedPlayer player) {
RESETTABLE.resetConnectionPhase(player); //When no handshake has taken place.
} //Test if the client supports CRP.
ClientResetType.CRP.doReset(player);
@Override
public boolean consideredComplete() {
return true;
} }
}, },
IN_PROGRESS { IN_PROGRESS {
}, },
RESETTABLE { WAITING_RESET() {
@Override
void onTransitionToNewPhase(ConnectedPlayer player) {
//Plugins may now send packets to client
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
}
@Override
public void resetConnectionPhase(ConnectedPlayer player) {
MinecraftConnection connection = player.getConnection();
//There is no going back even if the handshake fails. No reason to still be connected.
if (player.getConnectedServer() != null) {
player.getConnectedServer().disconnect();
player.setConnectedServer(null);
}
//Don't handle anything from the server until the reset has completed.
//player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
if (connection.getState() == StateRegistry.PLAY || connection.getState() == StateRegistry.CONFIG) {
connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
connection.setState(StateRegistry.LOGIN);
} else {
connection.write(new LoginPluginMessage(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
}
//Prepare to receive reset ACK
connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER,
ForgeConstants.RESET_LISTENER, new FML2CRPMResetCompleteDecoder());
//Transition
player.setPhase(WAITING_RESET);
WAITING_RESET.onTransitionToNewPhase(player);
}
@Override
public boolean consideredComplete() {
return true;
}
},
WAITING_RESET {
@Override @Override
void onTransitionToNewPhase(ConnectedPlayer player) { void onTransitionToNewPhase(ConnectedPlayer player) {
//We unregister so no plugin sees this client while the client is being reset. //We unregister so no plugin sees this client while the client is being reset.
@ -102,6 +59,7 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
@Override @Override
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) { public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
if (msg.getContext().getResponseID() == 98) { if (msg.getContext().getResponseID() == 98) {
//Reset complete
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER); player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
player.setPhase(NOT_STARTED); player.setPhase(NOT_STARTED);
@ -109,7 +67,10 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) { if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
// -> vanilla // -> vanilla
complete(player, ((Context.ClientContext) msg.getContext()).success()); complete(player, ((Context.ClientContext) msg.getContext()).success() ? ClientResetType.CRP : null);
}
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true); player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true);
} }
@ -120,8 +81,15 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
} }
}, },
COMPLETE { COMPLETE {
private ClientResetType resetType = ClientResetType.UNKNOWN;
@Override @Override
void onTransitionToNewPhase(ConnectedPlayer player) { void onTransitionToNewPhase(ConnectedPlayer player) {
//Send Login Success to client
MinecraftConnection connection = player.getConnection();
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
.sendPacket();
connection.setState(StateRegistry.PLAY);
//Plugins may now send packets to client //Plugins may now send packets to client
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE); player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
((VelocityServer) Ambassador.getInstance().server).registerConnection(player); ((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
@ -129,21 +97,7 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
@Override @Override
public void resetConnectionPhase(ConnectedPlayer player) { public void resetConnectionPhase(ConnectedPlayer player) {
Ambassador.getTemporaryForced().put(player.getUsername(), player.getConnectionInFlight().getServer(), getResetType().doReset(player);
Ambassador.getInstance().config.getServerSwitchCancellationTime(), TimeUnit.SECONDS);
//Disconnect - Reset
if(player.getModInfo().isPresent()
&& player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("serverredirect")
|| mod.getId().equals("srvredirect:red")))
&& player.getVirtualHost().isPresent()) {
ByteBuf buf = Unpooled.buffer();
ProtocolUtils.writeVarInt(buf, 0);
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
player.getConnection().write(new PluginMessage("srvredirect:red", buf));
} else {
player.disconnect(Ambassador.getInstance().config.getDisconnectResetMessage());
}
} }
@Override @Override
@ -151,14 +105,36 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
return true; return true;
} }
public void complete(ConnectedPlayer player, boolean resettable) { } @Override
public void complete(ConnectedPlayer player) {
if (Ambassador.getInstance().config.isDebugMode()) {
player.sendMessage(Component.text("Not resetting"));
}
}
@Override
void setResetType(ConnectedPlayer player, ClientResetType resetType) {
this.resetType = resetType;
if (Ambassador.getInstance().config.isDebugMode()) {
player.sendMessage(Component.text("Reset type: " + this.resetType.toString()));
}
}
@Override
public ClientResetType getResetType() {
return resetType;
}
}; };
//TODO: Make a new class that's linked to each player with these fields instead of having them in this phase class
public ForgeHandshake forgeHandshake = new ForgeHandshake(); public ForgeHandshake forgeHandshake = new ForgeHandshake();
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket<Context.ClientContext> msg, VelocityServerConnection server) { 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) { if (msg instanceof ModListReplyPacket replyPacket) {
ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map( ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map(
(v) -> new ModInfo.Mod(v,"1")).toList()); (v) -> new ModInfo.Mod(v,"1")).toList());
@ -179,33 +155,22 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
return true; return true;
} }
public void complete(ConnectedPlayer player) { public void complete(ConnectedPlayer player) {
complete(player, isResettable(player)); complete(player, null);
} }
public void complete(ConnectedPlayer player, boolean resettable) { public void complete(ConnectedPlayer player, ClientResetType resetType) {
MinecraftConnection connection = player.getConnection(); //Change phase to COMPLETE
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER)) player.setPhase(COMPLETE);
.sendPacket(); COMPLETE.onTransitionToNewPhase(player);
connection.setState(StateRegistry.PLAY); COMPLETE.forgeHandshake = forgeHandshake;
if (resetType != null) {
if (resettable) { COMPLETE.setResetType(player, resetType);
player.setPhase(RESETTABLE);
RESETTABLE.onTransitionToNewPhase(player);
RESETTABLE.forgeHandshake = forgeHandshake;
} else {
player.setPhase(COMPLETE);
COMPLETE.onTransitionToNewPhase(player);
COMPLETE.forgeHandshake = forgeHandshake;
} }
}
private boolean isResettable(ConnectedPlayer player) { if (Ambassador.getInstance().config.isDebugMode()) {
if (player.getModInfo().isPresent()) { player.sendMessage(Component.text("Forge handshake complete"));
return player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("clientresetpacket")));
} }
return false;
} }
void onTransitionToNewPhase(ConnectedPlayer player) { void onTransitionToNewPhase(ConnectedPlayer player) {
@ -221,5 +186,82 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
return false; return false;
} }
public ClientResetType getResetType() {
return COMPLETE.getResetType();
}
private ClientResetType getResetType(ConnectedPlayer player) {
if (Ambassador.getInstance().config.isDebugMode()) {
player.sendMessage(Component.text("Scanning modlist for client reset mods"));
}
if (player.getModInfo().isPresent()) {
if (player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("clientresetpacket")))) {
return ClientResetType.CRP;
} else if (Ambassador.getInstance().config.getServerSwitchCancellationTime() >= 0 &&
player.getModInfo().get().getMods().stream().anyMatch((mod -> mod.getId().equals("serverredirect")
|| mod.getId().equals("srvredirect:red")))
&& player.getVirtualHost().isPresent()) {
return ClientResetType.SR;
}
}
return ClientResetType.NONE;
}
void setResetType(ConnectedPlayer player, ClientResetType resetType) {
COMPLETE.setResetType(player, resetType);
}
public void updateResetType(ConnectedPlayer player) {
COMPLETE.setResetType(player, getResetType(player));
}
public enum ClientResetType {
UNKNOWN,
NONE,
CRP {
@Override
void doReset(ConnectedPlayer player) {
MinecraftConnection connection = player.getConnection();
//There is no going back even if the handshake fails. No reason to still be connected.
if (player.getConnectedServer() != null) {
player.getConnectedServer().disconnect();
player.setConnectedServer(null);
}
//Don't handle anything from the server until the reset has completed.
if (player.getConnectionInFlight() != null) {
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
}
if (connection.getState() == StateRegistry.PLAY || connection.getState() == StateRegistry.CONFIG) {
connection.write(new PluginMessagePacket("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
connection.setState(StateRegistry.LOGIN);
} else {
connection.write(new LoginPluginMessagePacket(98,"fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
}
//Prepare to receive reset ACK
connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER,
ForgeConstants.RESET_LISTENER, new FML2CRPMResetCompleteDecoder());
//Transition
player.setPhase(WAITING_RESET);
WAITING_RESET.onTransitionToNewPhase(player);
}
},
SR {
@Override
void doReset(ConnectedPlayer player) {
ByteBuf buf = Unpooled.buffer();
ProtocolUtils.writeVarInt(buf, 0);
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);
}
};
void doReset(ConnectedPlayer player) {
}
}
} }

View File

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

View File

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

View File

@ -1,10 +1,39 @@
package org.adde0109.ambassador.forge.packet; package org.adde0109.ambassador.forge.packet;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import io.netty.buffer.ByteBuf; 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) { 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)); registries.add(ProtocolUtils.readString(input, 32767));
List<String> dataPackRegistries = null; List<String> dataPackRegistries = null;
if (FML3) { if (FML3 && input.isReadable()) {
dataPackRegistries = new ArrayList<>(); dataPackRegistries = new ArrayList<>();
len = ProtocolUtils.readVarInt(input); len = ProtocolUtils.readVarInt(input);
for (int x = 0; x < len; x++) for (int x = 0; x < len; x++)

View File

@ -3,7 +3,6 @@ package org.adde0109.ambassador.forge.packet;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;

View File

@ -8,7 +8,7 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
import com.velocitypowered.proxy.util.except.QuietRuntimeException; import com.velocitypowered.proxy.util.except.QuietRuntimeException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
@ -43,7 +43,7 @@ public class CommandDecoderErrorCatcher extends ChannelInboundHandlerAdapter {
int packetId = ProtocolUtils.readVarInt(buf); int packetId = ProtocolUtils.readVarInt(buf);
MinecraftPacket packet = registry.createPacket(packetId); MinecraftPacket packet = registry.createPacket(packetId);
buf.readerIndex(originalReaderIndex); buf.readerIndex(originalReaderIndex);
if (packet instanceof AvailableCommands) { if (packet instanceof AvailableCommandsPacket) {
try { try {
((MinecraftDecoder) ctx.pipeline().get(Connections.MINECRAFT_DECODER)).channelRead(ctx, msg); ((MinecraftDecoder) ctx.pipeline().get(Connections.MINECRAFT_DECODER)).channelRead(ctx, msg);
} catch (QuietRuntimeException | CorruptedFrameException e) { } catch (QuietRuntimeException | CorruptedFrameException e) {

View File

@ -1,114 +1,145 @@
package org.adde0109.ambassador.forge.pipeline; package org.adde0109.ambassador.forge.pipeline;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse; import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket;
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder; import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.handler.codec.DecoderException; import io.netty.handler.codec.DecoderException;
import io.netty.handler.codec.MessageToMessageCodec; 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 org.adde0109.ambassador.forge.packet.*;
import java.util.ArrayList; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> { public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> {
private final boolean FML3; private final boolean FML3;
private final List<Integer> loginWrapperIDs = new ArrayList<>(); private final Map<Integer, Context> loginWrapperContexts = new HashMap<>();
public ForgeLoginWrapperCodec(boolean fml3) { public ForgeLoginWrapperCodec(boolean fml3) {
FML3 = 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 @Override
protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception { protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception {
ByteBuf buf = in.content(); ByteBuf buf = in.content();
Context context; Context context;
if (in instanceof LoginPluginMessage msg && msg.getChannel().equals("fml:loginwrapper")) { if (in instanceof LoginPluginResponsePacket msg) {
context = Context.createContext(msg.getId()); //Continue from stored context
} else if (in instanceof LoginPluginResponse msg && loginWrapperIDs.remove(Integer.valueOf(msg.getId()))) { context = Context.fromContext(
context = Context.createContext(msg.getId(), msg.isSuccess()); 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 { } else {
ctx.fireChannelRead(in.retain()); //New context.
return; 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(); int originalReaderIndex = buf.readerIndex();
try {
String channel = ProtocolUtils.readString(buf); if (!context.getChannelName().equals("fml:handshake")) {
if (!channel.equals("fml:handshake")) { out.add(GenericForgeLoginWrapperPacket.read(buf, context));
throw new DecoderException(); } else {
int length = ProtocolUtils.readVarInt(buf);
int packetID = ProtocolUtils.readVarInt(buf);
if (context instanceof Context.ClientContext clientContext) {
switch (packetID) {
case 2:
out.add(ModListReplyPacket.read(buf, clientContext));
break;
case 99:
out.add(new ACKPacket(clientContext));
break;
default:
//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 { } else {
int length = ProtocolUtils.readVarInt(buf); switch (packetID) {
int packetID = ProtocolUtils.readVarInt(buf); case 1:
if (context instanceof Context.ClientContext clientContext) { out.add(ModListPacket.read(buf, context, FML3));
switch (packetID) { break;
case 2: case 3:
out.add(ModListReplyPacket.read(buf, clientContext)); out.add(RegistryPacket.read(buf, context, FML3));
break;
case 4:
out.add(ConfigDataPacket.read(buf, context, FML3));
break;
case 5:
if (FML3) {
out.add(ModDataPacket.read(buf, context));
break; break;
case 99: }
out.add(new ACKPacket(clientContext)); default:
break; //Undo decoding
default: buf.readerIndex(originalReaderIndex);
throw new DecoderException(); out.add(GenericForgeLoginWrapperPacket.read(buf, context));
} if (Ambassador.getInstance().config.isDebugMode()) {
} else { Ambassador.getInstance().logger.warn(
switch (packetID) { "Unrecognised packet id received from server on fml:handshake: " + packetID);
case 1: }
out.add(ModListPacket.read(buf, context, FML3));
break;
case 3:
out.add(RegistryPacket.read(buf, context, FML3));
break;
case 4:
out.add(ConfigDataPacket.read(buf, context, FML3));
break;
case 5:
if (FML3) {
buf.readerIndex(originalReaderIndex);
out.add(ModDataPacket.read(buf, context));
break;
}
default:
throw new DecoderException();
}
} }
} }
} catch (DecoderException e) {
buf.readerIndex(originalReaderIndex);
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
} }
} }
@Override @Override
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception { protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
ByteBuf wrapped; ByteBuf wrapped;
if (msg instanceof GenericForgeLoginWrapperPacket<?>) {
wrapped = msg.encode(); boolean data = !(msg.getContext() instanceof Context.ClientContext clientContext && !clientContext.success());
} else {
String channel = "fml:handshake"; boolean includeLength = !(msg instanceof GenericForgeLoginWrapperPacket);
if (msg instanceof ForgeHandshakeUtils.SilentGearUtils.ACKPacket) {
channel = "silentgear:network"; String channel = msg.getContext().getChannelName();
}
wrapped = Unpooled.buffer(); wrapped = Unpooled.buffer();
if (data) {
ByteBuf encoded = msg.encode(); ByteBuf encoded = msg.encode();
ProtocolUtils.writeString(wrapped, channel); ProtocolUtils.writeString(wrapped, channel);
ProtocolUtils.writeVarInt(wrapped, encoded.readableBytes()); if (includeLength)
ProtocolUtils.writeVarInt(wrapped, encoded.readableBytes());
wrapped.writeBytes(encoded); wrapped.writeBytes(encoded);
encoded.release(); encoded.release();
} }
if (msg.getContext() instanceof Context.ClientContext clientContext) { if (msg.getContext() instanceof Context.ClientContext clientContext) {
out.add(new LoginPluginResponse(clientContext.getResponseID(), clientContext.success(), wrapped)); out.add(new LoginPluginResponsePacket(clientContext.getResponseID(), clientContext.success(), wrapped));
} else { } else {
out.add(new LoginPluginMessage(msg.getContext().getResponseID(), "fml:loginwrapper", wrapped)); out.add(new LoginPluginMessagePacket(msg.getContext().getResponseID(), "fml:loginwrapper", wrapped));
if (!(msg instanceof ModDataPacket)) { if (!(msg instanceof ModDataPacket)) { //ModDataPacket doesn't require a response
this.loginWrapperIDs.add(msg.getContext().getResponseID()); this.loginWrapperContexts.put(msg.getContext().getResponseID(), msg.getContext());
} }
} }
} }

View File

@ -71,6 +71,12 @@ public class VelocityEventHandler {
player.setModInfo(new ModInfo("Channels", event.getChannels().stream().map((id) -> { player.setModInfo(new ModInfo("Channels", event.getChannels().stream().map((id) -> {
return new ModInfo.Mod(id.getId(), ""); return new ModInfo.Mod(id.getId(), "");
}).toList())); }).toList()));
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
//If reset typ is still unknown, set it!
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.UNKNOWN) {
clientPhase.updateResetType(player);
}
} }
} }

View File

@ -6,7 +6,7 @@ import com.velocitypowered.proxy.connection.ConnectionTypes;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
import io.netty.channel.*; import io.netty.channel.*;
import io.netty.handler.codec.MessageToMessageEncoder; import io.netty.handler.codec.MessageToMessageEncoder;
import org.adde0109.ambassador.forge.ForgeConstants; import org.adde0109.ambassador.forge.ForgeConstants;
@ -16,17 +16,17 @@ import java.util.List;
import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN; import static com.velocitypowered.proxy.connection.forge.legacy.LegacyForgeConstants.HANDSHAKE_HOSTNAME_TOKEN;
public class FMLMarkerAdder extends MessageToMessageEncoder<Handshake> { public class FMLMarkerAdder extends MessageToMessageEncoder<HandshakePacket> {
final VelocityServer server; final VelocityServer server;
public FMLMarkerAdder(VelocityServer server) { public FMLMarkerAdder(VelocityServer server) {
super(Handshake.class); super(HandshakePacket.class);
this.server = server; this.server = server;
} }
@Override @Override
protected void encode(ChannelHandlerContext ctx, Handshake msg, List<Object> out) { protected void encode(ChannelHandlerContext ctx, HandshakePacket msg, List<Object> out) {
MinecraftConnection connection = (MinecraftConnection) ctx.pipeline().get(Connections.HANDLER); MinecraftConnection connection = (MinecraftConnection) ctx.pipeline().get(Connections.HANDLER);
VelocityServerConnection serverConnection = (VelocityServerConnection) connection.getAssociation(); VelocityServerConnection serverConnection = (VelocityServerConnection) connection.getAssociation();
PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode(); PlayerInfoForwarding forwardingMode = server.getConfiguration().getPlayerInfoForwardingMode();

View File

@ -6,9 +6,9 @@ import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.*; import com.velocitypowered.proxy.connection.backend.*;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage; import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import com.velocitypowered.proxy.util.except.QuietRuntimeException; import com.velocitypowered.proxy.util.except.QuietRuntimeException;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import org.adde0109.ambassador.forge.*; import org.adde0109.ambassador.forge.*;
@ -26,33 +26,28 @@ public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
} }
@Override @Override
public boolean handle(ServerLoginSuccess packet) { public boolean handle(ServerLoginSuccessPacket packet) {
if ((serverConnection.getPhase() instanceof VelocityForgeBackendConnectionPhase phase)) { if ((serverConnection.getPhase() instanceof VelocityForgeBackendConnectionPhase phase)) {
phase.onLoginSuccess(serverConnection,serverConnection.getPlayer()); phase.onLoginSuccess(serverConnection,serverConnection.getPlayer());
} }
original.handle(packet);
if (serverConnection.getConnection() == null) { original.handle(packet); //Can lead to disconnect.
return true;
} //If we are still connected after handling that package.
ConnectedPlayer player = serverConnection.getPlayer(); if (serverConnection.getConnection() != null) {
if (!(serverConnection.getConnection().getType() instanceof ForgeFMLConnectionType)) { ConnectedPlayer player = serverConnection.getPlayer();
if (player.getConnectedServer() == null ||
player.getConnectedServer().getConnection().getType() instanceof ForgeFMLConnectionType) { VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
//Initial Vanilla - test if the client can be reset clientPhase.complete(player);
//Forge -> vanilla
player.getPhase().resetConnectionPhase(player);
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
}
} else {
((VelocityForgeClientConnectionPhase) player.getPhase()).complete(player);
} }
return true; return true;
} }
@Override @Override
public boolean handle(Disconnect packet) { public boolean handle(DisconnectPacket packet) {
return original.handle(packet); return original.handle(packet);
} }

View File

@ -1,41 +0,0 @@
package org.adde0109.ambassador.velocity.backend;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.backend.TransitionSessionHandler;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.JoinGame;
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
public class ForgePlaySessionHandler implements MinecraftSessionHandler {
private final TransitionSessionHandler original;
private final VelocityServerConnection serverConnection;
public ForgePlaySessionHandler(TransitionSessionHandler original, VelocityServerConnection serverConnection) {
this.original = original;
this.serverConnection = serverConnection;
}
@Override
public boolean handle(JoinGame packet) {
if (serverConnection.getPlayer().getPhase() instanceof VelocityForgeClientConnectionPhase clientPhase) {
serverConnection.getPlayer().setPhase(VelocityForgeClientConnectionPhase.RESETTABLE);
}
return MinecraftSessionHandler.super.handle(packet);
}
@Override
public void disconnected() {
original.disconnected();
}
public void handleGeneric(MinecraftPacket packet) {
if (!packet.handle(original))
original.handleGeneric(packet);
}
public MinecraftSessionHandler getOriginal() {
return this.original;
}
}

View File

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

View File

@ -1,13 +1,13 @@
package org.adde0109.ambassador.velocity.client; package org.adde0109.ambassador.velocity.client;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess; import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
import io.netty.channel.ChannelHandlerContext; import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelOutboundHandlerAdapter; import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPromise; import io.netty.channel.ChannelPromise;
public class OutboundSuccessHolder extends ChannelOutboundHandlerAdapter { public class OutboundSuccessHolder extends ChannelOutboundHandlerAdapter {
private ServerLoginSuccess packet; private ServerLoginSuccessPacket packet;
private ChannelHandlerContext ctx; private ChannelHandlerContext ctx;
@Override @Override
@ -17,7 +17,7 @@ public class OutboundSuccessHolder extends ChannelOutboundHandlerAdapter {
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception { public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
if ((msg instanceof ServerLoginSuccess packet)) { if ((msg instanceof ServerLoginSuccessPacket packet)) {
this.packet = packet; this.packet = packet;
} else { } else {
ctx.write(msg, promise); ctx.write(msg, promise);

View File

@ -7,7 +7,7 @@ import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
import com.velocitypowered.proxy.network.Connections; import com.velocitypowered.proxy.network.Connections;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Handshake; import com.velocitypowered.proxy.protocol.packet.HandshakePacket;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import org.adde0109.ambassador.forge.ForgeConstants; import org.adde0109.ambassador.forge.ForgeConstants;
@ -22,7 +22,7 @@ public class VelocityHandshakeSessionHandler extends HandshakeSessionHandler {
} }
@Override @Override
public boolean handle(Handshake handshake) { public boolean handle(HandshakePacket handshake) {
handshake.handle(original); handshake.handle(original);
if (connection.getType() == ConnectionTypes.VANILLA) { if (connection.getType() == ConnectionTypes.VANILLA) {
final String[] markerSplit = handshake.getServerAddress().split("\0"); final String[] markerSplit = handshake.getServerAddress().split("\0");

View File

@ -1,9 +1,29 @@
# Do not change this # Do not change this
config-version = "1.1" config-version = "2.1"
# Message displayed to the player when disconnected from proxy during server switch.
# Legacy color codes and JSON are accepted.
disconnect-reset-message = "&6Please reconnect"
# How much time the player has to reconnect before canceling the server switch. (In seconds) # How much time the player has to reconnect before canceling the server switch. (In seconds)
server-switch-cancellation-time = 120 # Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
serverRedirectTimeout = 30
# Silence PCF absence warnings.
silence-warnings = false silence-warnings = false
# Allow server switches without reset even though the new server's registries don't match the old servers registry.
# Large modpacks often needs this set to true. Warning: This is a safety measure and setting this to true
# can lead to unstable behaviour.
bypass-registry-checks = false
# Allow player to switch without reset when the server's mods don't match. Even more unstable than bypassRegistryCheck.
# Warning: This is a safety measure for when bypassRegistryCheck is true. Setting this to also true can cause crashes.
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>"