Compare commits
67 Commits
v1.3.2-bet
...
non-api
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf12f90e7 | ||
|
|
b2a9eeffa1 | ||
|
|
eaa974593c | ||
|
|
0fcf6af72f | ||
|
|
c7c2e1cd33 | ||
|
|
9c824abd45 | ||
|
|
25c6152087 | ||
|
|
e6532b4e9d | ||
|
|
45e41a5b83 | ||
|
|
b91d767163 | ||
|
|
5ffea5bc96 | ||
|
|
3960efdba5 | ||
|
|
246b8131b3 | ||
|
|
4b838990a6 | ||
|
|
89372a4a3c | ||
|
|
34ef23593d | ||
|
|
8767d55873 | ||
|
|
cd6c0be56e | ||
|
|
6dfbdecfaf | ||
|
|
550328863a | ||
|
|
7af0f5c880 | ||
|
|
222b22eb3c | ||
|
|
3fa30b7362 | ||
|
|
cafa8adffd | ||
|
|
692e7c9e79 | ||
|
|
503cc52dba | ||
|
|
82f5729da4 | ||
|
|
dd89f5e07c | ||
|
|
55e0a66122 | ||
|
|
ba3132c1bd | ||
|
|
fa97806e2e | ||
|
|
161700c7e6 | ||
|
|
c3ae255be6 | ||
|
|
57282b69ab | ||
|
|
82f3e1ba5f | ||
|
|
669a856ec4 | ||
|
|
0e5f3feb49 | ||
|
|
f9ea48f93b | ||
|
|
28ebcd76fa | ||
|
|
594aa4ccea | ||
|
|
4510f15ebb | ||
|
|
30275d8895 | ||
|
|
14c23af7c2 | ||
|
|
6983b7abf1 | ||
|
|
172422a315 | ||
|
|
e7fd073652 | ||
|
|
fbc15bc4c8 | ||
|
|
f94488ae85 | ||
|
|
652a16ea32 | ||
|
|
1b25702c6b | ||
|
|
4b5d9131f4 | ||
|
|
7ab96fcbb4 | ||
|
|
07547130be | ||
|
|
9c5f49f6e9 | ||
|
|
cd8df2c5a0 | ||
|
|
1b59fbae1a | ||
|
|
b8c905e619 | ||
|
|
e754f58623 | ||
|
|
7f434a8c5e | ||
|
|
a0b38b7595 | ||
|
|
f1b1cc463c | ||
|
|
a9cf6b349e | ||
|
|
1b223a119f | ||
|
|
30e90556ff | ||
|
|
cfa7ecad21 | ||
|
|
8a3f260375 | ||
|
|
bee74b68b9 |
23
README.md
23
README.md
|
|
@ -3,22 +3,27 @@
|
||||||
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".
|
||||||
3. If you want to use modern forwarding you can use this mod on the Forge server: https://github.com/adde0109/Proxy-Compatible-Forge
|
3. If you want to use player-information forwarding you can use any of these mods on the 1.13+ forge server:
|
||||||
|
- https://github.com/adde0109/Proxy-Compatible-Forge (Modern forwarding) (Magma 1.18.2 and higher includes this)
|
||||||
|
|
||||||
|
- 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)
|
||||||
* Server switching using client mod for instant server switching: https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
|
* 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":
|
## Stuck on "Negotiating":
|
||||||
Why: This is happening because the client finishes the reset after the reset-timeout time has passed and thus, the proxy disconnects the client and the screen. Usually because to the client took too long to reset the forge registries.
|
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.)
|
||||||
|
|
||||||
Fix: Increase the "reset-timeout" in the config.
|
|
||||||
If you know that everyone who is connecting to the server is using the packet client reset packet mod (Pixelmon include this mod), you can set this value to the max (Velocity's timout found in Velocity.toml).
|
|
||||||
This timout is meant to allow for people that doesn't have the mod to get disconnected more quickly.
|
|
||||||
|
|
||||||
## Discord
|
## Discord
|
||||||
https://discord.gg/Vusz9pBNyJ
|
https://discord.gg/Vusz9pBNyJ
|
||||||
|
|
|
||||||
2
Velocity
2
Velocity
|
|
@ -1 +1 @@
|
||||||
Subproject commit 36cc2bde23e7d50545c04b4060a52f3e6b461368
|
Subproject commit c3583e182ca6585e40d1eef0da8c18547c0b1bc1
|
||||||
|
|
@ -5,7 +5,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.adde0109"
|
group = "org.adde0109"
|
||||||
version = "1.3.2-beta"
|
version = "1.5.3-beta"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
BIN
gradle/wrapper/gradle-wrapper.jar
vendored
Binary file not shown.
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
|
|
@ -1,6 +1,6 @@
|
||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.0.2-bin.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip
|
||||||
networkTimeout=10000
|
networkTimeout=10000
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
|
|
||||||
7
gradlew
vendored
7
gradlew
vendored
|
|
@ -85,9 +85,6 @@ done
|
||||||
APP_BASE_NAME=${0##*/}
|
APP_BASE_NAME=${0##*/}
|
||||||
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
|
||||||
|
|
||||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
|
||||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
|
||||||
|
|
||||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
MAX_FD=maximum
|
MAX_FD=maximum
|
||||||
|
|
||||||
|
|
@ -197,6 +194,10 @@ if "$cygwin" || "$msys" ; then
|
||||||
done
|
done
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||||
|
|
||||||
# Collect all arguments for the java command;
|
# Collect all arguments for the java command;
|
||||||
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
|
||||||
# shell script including quotes and variable substitutions, so put them in
|
# shell script including quotes and variable substitutions, so put them in
|
||||||
|
|
|
||||||
|
|
@ -18,12 +18,19 @@ 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.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.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.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;
|
||||||
|
|
@ -37,12 +44,14 @@ import java.nio.file.Path;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.api.network.ProtocolVersion.MINECRAFT_1_19_3;
|
|
||||||
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.3.2-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
|
||||||
|
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;
|
||||||
private final Metrics.Factory metricsFactory;
|
private final Metrics.Factory metricsFactory;
|
||||||
|
|
@ -67,22 +76,36 @@ public class Ambassador {
|
||||||
Ambassador.instance = this;
|
Ambassador.instance = this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean checkCompatibleVersion() {
|
||||||
|
//Update this when changing minVelocityVersion
|
||||||
|
try {
|
||||||
|
Class.forName("com.velocitypowered.proxy.protocol.packet.DisconnectPacket");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.LAST)
|
@Subscribe(order = PostOrder.LAST)
|
||||||
public void onProxyInitialization(ProxyInitializeEvent event) {
|
public void onProxyInitialization(ProxyInitializeEvent event) {
|
||||||
initMetrics();
|
initMetrics();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
if (!checkCompatibleVersion()) {
|
||||||
|
logger.error("Incompatible velocity version! Please use '" + minVelocityVersion + "' or higher.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Files.createDirectories(dataDirectory);
|
Files.createDirectories(dataDirectory);
|
||||||
|
|
||||||
Path configPath = dataDirectory.resolve("Ambassador.toml");
|
Path configPath = dataDirectory.resolve("Ambassador.toml");
|
||||||
config = AmbassadorConfig.read(configPath);
|
config = AmbassadorConfig.read(configPath);
|
||||||
config.validate();
|
|
||||||
|
|
||||||
inject();
|
inject();
|
||||||
|
|
||||||
server.getEventManager().register(this, new VelocityEventHandler(this));
|
server.getEventManager().register(this, new VelocityEventHandler(this));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
logger.error(e.toString());
|
logger.error("An error prevented Ambassador to load correctly: "+ e.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -91,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) {
|
||||||
|
|
@ -104,11 +126,13 @@ public class Ambassador {
|
||||||
Field cmField = VelocityServer.class.getDeclaredField("cm");
|
Field cmField = VelocityServer.class.getDeclaredField("cm");
|
||||||
cmField.setAccessible(true);
|
cmField.setAccessible(true);
|
||||||
|
|
||||||
ChannelInitializer<?> original = ((ConnectionManager) cmField.get(server)).serverChannelInitializer.get();
|
ConnectionManager connectionManager = (ConnectionManager) cmField.get(server);
|
||||||
((ConnectionManager) cmField.get(server)).serverChannelInitializer.set(new VelocityServerChannelInitializer(original,(VelocityServer) server));
|
|
||||||
|
|
||||||
ChannelInitializer<?> originalBackend = ((ConnectionManager) cmField.get(server)).backendChannelInitializer.get();
|
ChannelInitializer<Channel> original = connectionManager.getServerChannelInitializer().get();
|
||||||
((ConnectionManager) cmField.get(server)).backendChannelInitializer.set(new VelocityBackendChannelInitializer(originalBackend,(VelocityServer) server));
|
connectionManager.getServerChannelInitializer().set(new VelocityServerChannelInitializer(original, (VelocityServer) server));
|
||||||
|
|
||||||
|
ChannelInitializer<Channel> originalBackend = connectionManager.getBackendChannelInitializer().get();
|
||||||
|
connectionManager.getBackendChannelInitializer().set(new VelocityBackendChannelInitializer(originalBackend, (VelocityServer) server));
|
||||||
|
|
||||||
Method argumentRegistry = ArgumentPropertyRegistry.class.getDeclaredMethod("register", ArgumentIdentifier.class, Class.class, ArgumentPropertySerializer.class);
|
Method argumentRegistry = ArgumentPropertyRegistry.class.getDeclaredMethod("register", ArgumentIdentifier.class, Class.class, ArgumentPropertySerializer.class);
|
||||||
argumentRegistry.setAccessible(true);
|
argumentRegistry.setAccessible(true);
|
||||||
|
|
@ -132,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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,46 +7,43 @@ 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
|
@Expose
|
||||||
private int resetTimeout = 1000;
|
private int serverSwitchCancellationTime = 30;
|
||||||
|
|
||||||
@Expose
|
|
||||||
private String disconnectResetMessage = "Please reconnect";
|
|
||||||
|
|
||||||
@Expose
|
|
||||||
private int serverSwitchCancellationTime = 120;
|
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean silenceWarnings = false;
|
private boolean silenceWarnings = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassRegistryCheck = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassModCheck = false;
|
||||||
|
|
||||||
private net.kyori.adventure.text.@MonotonicNonNull Component messageAsAsComponent;
|
@Expose
|
||||||
|
private boolean debugMode = false;
|
||||||
|
|
||||||
private AmbassadorConfig(int resetTimeout, String kickResetMessage, int serverSwitchCancellationTime, boolean silenceWarnings) {
|
@Expose
|
||||||
this.resetTimeout = resetTimeout;
|
private boolean enableKickReset = false;
|
||||||
this.disconnectResetMessage = kickResetMessage;
|
|
||||||
this.serverSwitchCancellationTime = serverSwitchCancellationTime;
|
@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.silenceWarnings = silenceWarnings;
|
||||||
};
|
this.bypassRegistryCheck = bypassRegistryCheck;
|
||||||
|
this.bypassModCheck = bypassModCheck;
|
||||||
public void validate() {
|
this.debugMode = debugMode;
|
||||||
final int connectionTimeout = Ambassador.getInstance().server.getConfiguration().getReadTimeout();
|
this.enableKickReset = enableKickReset;
|
||||||
if (resetTimeout >= connectionTimeout) {
|
this.kickReconnectMessageString = kickReconnectMessageString;
|
||||||
throw new InvalidValueException("'reset-timeout' can't be more than nor equal to 'read-timeout': reset-timeout=" + resetTimeout + " connection-timeout=" + connectionTimeout);
|
|
||||||
}
|
|
||||||
if (resetTimeout <= 0) {
|
|
||||||
throw new InvalidValueException("'reset-timeout' can't be less than nor equal to zero: reset-timeout=" + resetTimeout);
|
|
||||||
}
|
|
||||||
if (serverSwitchCancellationTime <= 0) {
|
|
||||||
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) {
|
public static AmbassadorConfig read(Path path) throws IOException {
|
||||||
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
||||||
.getResource("default-ambassador.toml");
|
.getResource("default-ambassador.toml");
|
||||||
if (defaultConfigLocation == null) {
|
if (defaultConfigLocation == null) {
|
||||||
|
|
@ -68,33 +65,41 @@ public class AmbassadorConfig {
|
||||||
configVersion = 1.0;
|
configVersion = 1.0;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (configVersion < 1.1) {
|
|
||||||
config.set("silence-warnings", false);
|
|
||||||
config.set("config-version", "1.1");
|
|
||||||
}
|
|
||||||
|
|
||||||
int resetTimeout = config.getIntOrElse("reset-timeout", 3000);
|
|
||||||
String kickResetMessage = config.getOrElse("disconnect-reset-message", "Please reconnect");
|
|
||||||
int serverSwitchCancellationTime = config.getIntOrElse("server-switch-cancellation-time", 120);
|
|
||||||
|
|
||||||
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
||||||
|
|
||||||
return new AmbassadorConfig(resetTimeout, kickResetMessage, serverSwitchCancellationTime, silenceWarnings);
|
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);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getResetTimeout() {
|
boolean enableKickReset = config.getOrElse("enable-kick-reset", false);
|
||||||
return resetTimeout;
|
|
||||||
}
|
|
||||||
|
|
||||||
public net.kyori.adventure.text.Component getDisconnectResetMessage() {
|
return new AmbassadorConfig(silenceWarnings, bypassRegistryCheck, bypassModCheck,
|
||||||
if (messageAsAsComponent == null) {
|
debugMode, enableKickReset, kickReconnectMessageString);
|
||||||
if (disconnectResetMessage.startsWith("{")) {
|
|
||||||
messageAsAsComponent = GsonComponentSerializer.gson().deserialize(disconnectResetMessage);
|
|
||||||
} else {
|
|
||||||
messageAsAsComponent = LegacyComponentSerializer.legacyAmpersand().deserialize(disconnectResetMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return messageAsAsComponent;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getServerSwitchCancellationTime() {
|
public int getServerSwitchCancellationTime() {
|
||||||
|
|
@ -104,4 +109,24 @@ public class AmbassadorConfig {
|
||||||
public boolean isSilenceWarnings() {
|
public boolean isSilenceWarnings() {
|
||||||
return silenceWarnings;
|
return silenceWarnings;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isBypassRegistryCheck() {
|
||||||
|
return bypassRegistryCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isBypassModCheck() {
|
||||||
|
return bypassModCheck;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isDebugMode() {
|
||||||
|
return debugMode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnableKickReset() {
|
||||||
|
return enableKickReset;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getKickReconnectMessageString() {
|
||||||
|
return kickReconnectMessageString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ public class ForgeConstants {
|
||||||
public static final String RESET_LISTENER = "ambassador-reset-listener";
|
public static final String RESET_LISTENER = "ambassador-reset-listener";
|
||||||
public static final String SERVER_SUCCESS_LISTENER = "ambassador-server-success-listener";
|
public static final String SERVER_SUCCESS_LISTENER = "ambassador-server-success-listener";
|
||||||
public static final String PLUGIN_PACKET_QUEUE = "ambassador-plugin-generated-packet-queue";
|
public static final String PLUGIN_PACKET_QUEUE = "ambassador-plugin-generated-packet-queue";
|
||||||
public static final String FORGE_HANDSHAKE_HOLDER = "ambassador-forge-handshake-holder";
|
public static final String LOGIN_PACKET_QUEUE = "ambassador-login-packet-queue";
|
||||||
public static final String FORGE_HANDSHAKE_DECODER = "ambassador-forge-decoder";
|
public static final String FORGE_HANDSHAKE_DECODER = "ambassador-forge-decoder";
|
||||||
public static final String FORGE_HANDSHAKE_HANDLER = "ambassador-forge-handler";
|
public static final String FORGE_HANDSHAKE_HANDLER = "ambassador-forge-handler";
|
||||||
public static final String COMMAND_ERROR_CATCHER = "ambassador-command-catcher";
|
public static final String COMMAND_ERROR_CATCHER = "ambassador-command-catcher";
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ public class ForgeFMLConnectionType implements ConnectionType {
|
||||||
@Override
|
@Override
|
||||||
public GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType) {
|
public GameProfile addGameProfileTokensIfRequired(GameProfile original, PlayerInfoForwarding forwardingType) {
|
||||||
//This is meant for Arclight to parse
|
//This is meant for Arclight to parse
|
||||||
if (forwardingType == PlayerInfoForwarding.LEGACY) {
|
if (forwardingType == PlayerInfoForwarding.LEGACY || forwardingType == PlayerInfoForwarding.BUNGEEGUARD) {
|
||||||
return original.addProperties(Collections.singleton(new GameProfile.Property("extraData", "\1FML" + netVersion + "\1", "")));
|
return original.addProperties(Collections.singleton(new GameProfile.Property("extraData", "\1FML" + netVersion + "\1", "")));
|
||||||
} else {
|
} else {
|
||||||
return original;
|
return original;
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,42 @@
|
||||||
|
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;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.zip.Adler32;
|
||||||
|
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() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public ModListReplyPacket getModListReplyPacket() {
|
||||||
|
return modListReplyPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setModListReplyPacket(ModListReplyPacket modListReplyPacket) {
|
||||||
|
this.modListReplyPacket = modListReplyPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void addRegistry(RegistryPacket packet) {
|
||||||
|
Checksum registryChecksum = new Adler32();
|
||||||
|
registryChecksum.update(packet.getSnapshot());
|
||||||
|
registries.put(packet.getRegistryName(), registryChecksum.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Long> getRegistries() {
|
||||||
|
return registries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isCompatible(ForgeHandshake handshake) {
|
||||||
|
return this.registries.equals(handshake.registries);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,12 @@ package org.adde0109.ambassador.forge;
|
||||||
import com.google.common.io.ByteArrayDataInput;
|
import com.google.common.io.ByteArrayDataInput;
|
||||||
import com.google.common.io.ByteArrayDataOutput;
|
import com.google.common.io.ByteArrayDataOutput;
|
||||||
import com.google.common.io.ByteStreams;
|
import com.google.common.io.ByteStreams;
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
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;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
@ -71,13 +77,23 @@ public class ForgeHandshakeUtils {
|
||||||
return dataAndPacketIdStream.toByteArray();
|
return dataAndPacketIdStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final byte[] emptyModlist = generateEmptyModlist();
|
public static final byte[] emptyModlistFML2 = generateEmptyModlist(2);
|
||||||
private static byte[] generateEmptyModlist() {
|
public static final byte[] emptyModlistFML3 = generateEmptyModlist(3);
|
||||||
|
private static byte[] generateEmptyModlist(int fmlVersion) {
|
||||||
ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput();
|
ByteArrayDataOutput dataAndPacketIdStream = ByteStreams.newDataOutput();
|
||||||
writeVarInt(dataAndPacketIdStream,1);
|
writeVarInt(dataAndPacketIdStream,1);
|
||||||
writeVarInt(dataAndPacketIdStream,0);
|
writeVarInt(dataAndPacketIdStream,0); //Mods
|
||||||
writeVarInt(dataAndPacketIdStream,0);
|
if (fmlVersion == 3) {
|
||||||
writeVarInt(dataAndPacketIdStream,0);
|
writeVarInt(dataAndPacketIdStream,1); //Channels
|
||||||
|
writeUtf(dataAndPacketIdStream, "forge:tier_sorting");
|
||||||
|
writeUtf(dataAndPacketIdStream,"1.0");
|
||||||
|
writeVarInt(dataAndPacketIdStream,0); //Registries
|
||||||
|
writeVarInt(dataAndPacketIdStream,0); //Data-packs
|
||||||
|
} else {
|
||||||
|
writeVarInt(dataAndPacketIdStream,0); //Channels
|
||||||
|
writeVarInt(dataAndPacketIdStream,0); //Registries
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
ByteArrayDataOutput stream = ByteStreams.newDataOutput();
|
ByteArrayDataOutput stream = ByteStreams.newDataOutput();
|
||||||
byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray();
|
byte[] dataAndPacketId = dataAndPacketIdStream.toByteArray();
|
||||||
|
|
@ -100,4 +116,61 @@ public class ForgeHandshakeUtils {
|
||||||
stream.write(dataAndPacketId);
|
stream.write(dataAndPacketId);
|
||||||
return stream.toByteArray();
|
return stream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static class ThirdPartyRegistryUtils {
|
||||||
|
|
||||||
|
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, packetID);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context.ClientContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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 {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,17 +6,21 @@ 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.LoginPluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import org.adde0109.ambassador.Ambassador;
|
||||||
|
import org.adde0109.ambassador.forge.packet.*;
|
||||||
import org.adde0109.ambassador.forge.pipeline.CommandDecoderErrorCatcher;
|
import org.adde0109.ambassador.forge.pipeline.CommandDecoderErrorCatcher;
|
||||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
|
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
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
|
||||||
|
|
@ -25,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);
|
||||||
|
|
@ -45,35 +49,119 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
COMPLETE() {
|
COMPLETE {
|
||||||
@Override
|
@Override
|
||||||
public boolean consideredComplete() {
|
public boolean consideredComplete() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
public ForgeHandshake handshake = new ForgeHandshake();
|
||||||
|
CountDownLatch remainingRegistries;
|
||||||
|
|
||||||
VelocityForgeBackendConnectionPhase() {
|
VelocityForgeBackendConnectionPhase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(VelocityServerConnection server, ConnectedPlayer player, LoginPluginMessage 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 (!player.isActive()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!clientPhase.consideredComplete()) {
|
||||||
|
//Initial Forge
|
||||||
|
if (message instanceof ModListPacket modListPacket) {
|
||||||
|
clientPhase.forgeHandshake = new ForgeHandshake();
|
||||||
|
}
|
||||||
|
if (message instanceof RegistryPacket registryPacket) {
|
||||||
|
clientPhase.forgeHandshake.addRegistry(registryPacket);
|
||||||
|
}
|
||||||
|
player.getConnection().write(message);
|
||||||
|
} else {
|
||||||
|
//Reset client if not ready to receive new handshake
|
||||||
|
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.CRP ||
|
||||||
|
clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.SR) {
|
||||||
clientPhase.resetConnectionPhase(player);
|
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) {
|
||||||
|
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(() -> {
|
||||||
|
try {
|
||||||
|
remainingRegistries.await();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}).thenAcceptAsync((v) -> {
|
||||||
|
|
||||||
|
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());
|
||||||
|
} 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 " +
|
||||||
|
player.getConnectedServer().getServer().getServerInfo().getName());
|
||||||
|
server.disconnect();
|
||||||
|
}
|
||||||
|
}, server.ensureConnected().eventLoop());
|
||||||
|
} else if (message instanceof RegistryPacket registryPacket) {
|
||||||
|
server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
|
||||||
|
handshake.addRegistry(registryPacket);
|
||||||
|
remainingRegistries.countDown();
|
||||||
|
} else if (message instanceof ConfigDataPacket) {
|
||||||
|
server.getConnection().write(new 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
|
//Forge server
|
||||||
//To avoid unnecessary resets, we wait until we get the handshake even if we know that we should
|
//To avoid unnecessary resets, we wait until we get the handshake even if we know that we should
|
||||||
//reset because that the previous server was Forge.
|
//reset because that the previous server was Forge.
|
||||||
player.getConnection().write(message.retain());
|
|
||||||
|
|
||||||
ForgeLoginWrapperDecoder decoder = (ForgeLoginWrapperDecoder) player.getConnection()
|
|
||||||
.getChannel().pipeline().get(ForgeConstants.FORGE_HANDSHAKE_DECODER);
|
|
||||||
decoder.registerLoginWrapperID(message.getId());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) {
|
public void onLoginSuccess(VelocityServerConnection serverCon, ConnectedPlayer player) {
|
||||||
|
|
@ -87,7 +175,7 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
}
|
}
|
||||||
|
|
||||||
private VelocityForgeBackendConnectionPhase getNewPhase(VelocityServerConnection serverConnection,
|
private VelocityForgeBackendConnectionPhase getNewPhase(VelocityServerConnection serverConnection,
|
||||||
LoginPluginMessage packet) {
|
IForgeLoginWrapperPacket<Context> packet) {
|
||||||
VelocityForgeBackendConnectionPhase phaseToTransitionTo = nextPhase();
|
VelocityForgeBackendConnectionPhase phaseToTransitionTo = nextPhase();
|
||||||
if (phaseToTransitionTo != this) {
|
if (phaseToTransitionTo != this) {
|
||||||
phaseToTransitionTo.onTransitionToNewPhase(serverConnection);
|
phaseToTransitionTo.onTransitionToNewPhase(serverConnection);
|
||||||
|
|
@ -96,11 +184,11 @@ 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().getSessionHandler().handle(packet);
|
server.getConnection().getActiveSessionHandler().handle(packet);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
|
|
||||||
|
|
@ -1,132 +1,177 @@
|
||||||
package org.adde0109.ambassador.forge;
|
package org.adde0109.ambassador.forge;
|
||||||
|
|
||||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
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.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.ClientConnectionPhase;
|
import com.velocitypowered.proxy.connection.client.ClientConnectionPhase;
|
||||||
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.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
|
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.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.forge.pipeline.ForgeLoginWrapperDecoder;
|
|
||||||
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
|
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
|
||||||
import org.adde0109.ambassador.velocity.client.OutboundForgeHandshakeQueue;
|
|
||||||
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
||||||
import org.adde0109.ambassador.velocity.client.PluginLoginPacketQueue;
|
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
||||||
|
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.ScheduledFuture;
|
|
||||||
|
|
||||||
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
||||||
//TODO:Make class when PCF is done
|
|
||||||
|
|
||||||
NOT_STARTED {
|
NOT_STARTED {
|
||||||
@Override
|
@Override
|
||||||
VelocityForgeClientConnectionPhase nextPhase() {
|
VelocityForgeClientConnectionPhase nextPhase() {
|
||||||
return IN_PROGRESS;
|
return IN_PROGRESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void complete(ConnectedPlayer player) {
|
||||||
|
//When no handshake has taken place.
|
||||||
|
//Test if the client supports CRP.
|
||||||
|
ClientResetType.CRP.doReset(player);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
IN_PROGRESS {
|
IN_PROGRESS {
|
||||||
|
},
|
||||||
|
WAITING_RESET() {
|
||||||
|
@Override
|
||||||
|
void onTransitionToNewPhase(ConnectedPlayer player) {
|
||||||
|
//We unregister so no plugin sees this client while the client is being reset.
|
||||||
|
((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player);
|
||||||
|
player.getConnection().getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,
|
||||||
|
ForgeConstants.LOGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.PLAY));
|
||||||
|
if (player.getConnection().getChannel().pipeline().get(ForgeConstants.PLUGIN_PACKET_QUEUE) == null)
|
||||||
|
player.getConnection().getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,
|
||||||
|
ForgeConstants.PLUGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.LOGIN));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
|
||||||
|
if (msg.getContext().getResponseID() == 98) {
|
||||||
|
//Reset complete
|
||||||
|
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
|
||||||
|
player.setPhase(NOT_STARTED);
|
||||||
|
|
||||||
|
player.getConnection().getChannel().pipeline().remove(ForgeConstants.LOGIN_PACKET_QUEUE);
|
||||||
|
|
||||||
|
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||||
|
// -> vanilla
|
||||||
|
complete(player, ((Context.ClientContext) msg.getContext()).success() ? ClientResetType.CRP : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (player.getConnectionInFlight() != null) {
|
||||||
|
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
COMPLETE {
|
COMPLETE {
|
||||||
|
|
||||||
|
private ClientResetType resetType = ClientResetType.UNKNOWN;
|
||||||
|
@Override
|
||||||
|
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
|
||||||
|
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
|
||||||
|
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void resetConnectionPhase(ConnectedPlayer player) {
|
public void resetConnectionPhase(ConnectedPlayer player) {
|
||||||
MinecraftConnection connection = player.getConnection();
|
getResetType().doReset(player);
|
||||||
|
|
||||||
|
|
||||||
//We unregister so no plugin sees this client while the client is being reset.
|
|
||||||
((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player);
|
|
||||||
if (player.getConnectedServer() != null) {
|
|
||||||
player.getConnectedServer().disconnect();
|
|
||||||
player.setConnectedServer(null);
|
|
||||||
}
|
|
||||||
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(false);
|
|
||||||
|
|
||||||
//Prepare to receive reset ACK and Forge Handshake.
|
|
||||||
connection.getChannel().pipeline().addBefore(Connections.MINECRAFT_DECODER, ForgeConstants.RESET_LISTENER,new FML2CRPMResetCompleteDecoder());
|
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER, ForgeConstants.FORGE_HANDSHAKE_HOLDER,new OutboundForgeHandshakeQueue());
|
|
||||||
((ForgeLoginWrapperDecoder) connection.getChannel().pipeline().get(ForgeConstants.FORGE_HANDSHAKE_DECODER)).registerLoginWrapperID(98);
|
|
||||||
|
|
||||||
//No more PLAY packets past this point should be sent to the client in case the reset works.
|
|
||||||
connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generatePluginResetPacket())));
|
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
|
|
||||||
|
|
||||||
player.setPhase(WAITING_RESET);
|
|
||||||
WAITING_RESET.onTransitionToNewPhase(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean consideredComplete() {
|
public boolean consideredComplete() {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
},
|
|
||||||
WAITING_RESET {
|
|
||||||
|
|
||||||
ScheduledFuture<?> scheduledFuture;
|
|
||||||
@Override
|
@Override
|
||||||
void onTransitionToNewPhase(ConnectedPlayer player) {
|
public void complete(ConnectedPlayer player) {
|
||||||
scheduledFuture = player.getConnection().eventLoop().schedule(()-> {
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
|
player.sendMessage(Component.text("Not resetting"));
|
||||||
Ambassador.getTemporaryForced().put(player.getUsername(), player.getConnectionInFlight().getServer(),
|
}
|
||||||
Ambassador.getInstance().config.getServerSwitchCancellationTime(), TimeUnit.SECONDS);
|
|
||||||
//Disconnect - Reset Timeout
|
|
||||||
player.disconnect(Ambassador.getInstance().config.getDisconnectResetMessage());
|
|
||||||
}, Ambassador.getInstance().config.getResetTimeout(), TimeUnit.MILLISECONDS);
|
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
|
|
||||||
if (msg.getId() == 98) {
|
|
||||||
if (scheduledFuture.cancel(false)) {
|
|
||||||
player.getConnection().getChannel().pipeline().remove(ForgeConstants.RESET_LISTENER);
|
|
||||||
player.getConnection().setState(StateRegistry.LOGIN);
|
|
||||||
player.setPhase(NOT_STARTED);
|
|
||||||
//Send all held messages
|
|
||||||
player.getConnection().getChannel().pipeline().remove(ForgeConstants.FORGE_HANDSHAKE_HOLDER);
|
|
||||||
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true);
|
|
||||||
|
|
||||||
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
@Override
|
||||||
//Forge -> Vanilla
|
void setResetType(ConnectedPlayer player, ClientResetType resetType) {
|
||||||
MinecraftConnection connection = player.getConnection();
|
this.resetType = resetType;
|
||||||
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
.sendPacket();
|
player.sendMessage(Component.text("Reset type: " + this.resetType.toString()));
|
||||||
connection.setState(StateRegistry.PLAY);
|
|
||||||
connection.getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
|
|
||||||
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
|
||||||
} else {
|
@Override
|
||||||
return false;
|
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 boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket<Context.ClientContext> msg, VelocityServerConnection server) {
|
||||||
|
|
||||||
|
if (msg.getContext().getChannelName().equals("zeta:main")) {
|
||||||
public boolean vanillaMode = true;
|
forgeHandshake.zetaFlagsPacket = (GenericForgeLoginWrapperPacket<Context.ClientContext>) msg;
|
||||||
|
}
|
||||||
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket msg, VelocityServerConnection server) {
|
|
||||||
player.setPhase(nextPhase());
|
|
||||||
|
|
||||||
if (msg instanceof ModListReplyPacket replyPacket) {
|
if (msg instanceof ModListReplyPacket replyPacket) {
|
||||||
|
ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map(
|
||||||
|
(v) -> new ModInfo.Mod(v,"1")).toList());
|
||||||
|
player.setModInfo(modInfo);
|
||||||
|
forgeHandshake.setModListReplyPacket(replyPacket);
|
||||||
|
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||||
|
complete(player);
|
||||||
|
player.getConnectionInFlight().getConnection().getChannel().config().setAutoRead(true);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
replyPacket.getChannels().put(MinecraftChannelIdentifier.from("ambassador:commands"),"1");
|
replyPacket.getChannels().put(MinecraftChannelIdentifier.from("ambassador:commands"),"1");
|
||||||
}
|
}
|
||||||
|
|
||||||
player.getConnectionInFlight().getConnection().write(msg.encode());
|
player.getConnectionInFlight().getConnection().write(msg);
|
||||||
vanillaMode = false;
|
|
||||||
|
player.setPhase(nextPhase());
|
||||||
|
nextPhase().forgeHandshake = this.forgeHandshake;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
public void complete(ConnectedPlayer player) {
|
||||||
|
complete(player, null);
|
||||||
|
}
|
||||||
|
|
||||||
private RegisteredServer lastKnownWorking;
|
public void complete(ConnectedPlayer player, ClientResetType resetType) {
|
||||||
|
//Change phase to COMPLETE
|
||||||
|
player.setPhase(COMPLETE);
|
||||||
|
COMPLETE.onTransitionToNewPhase(player);
|
||||||
|
COMPLETE.forgeHandshake = forgeHandshake;
|
||||||
|
if (resetType != null) {
|
||||||
|
COMPLETE.setResetType(player, resetType);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
|
player.sendMessage(Component.text("Forge handshake complete"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void onTransitionToNewPhase(ConnectedPlayer player) {
|
void onTransitionToNewPhase(ConnectedPlayer player) {
|
||||||
|
|
||||||
|
|
@ -141,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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
public class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
|
||||||
|
|
||||||
|
private final Context.ClientContext context;
|
||||||
|
|
||||||
|
public ACKPacket(Context.ClientContext context) {
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ByteBuf encode() {
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, 99);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context.ClientContext getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,43 @@
|
||||||
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
public class ConfigDataPacket implements IForgeLoginWrapperPacket<Context> {
|
||||||
|
|
||||||
|
private final String fileName;
|
||||||
|
private final byte[] fileData;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
public ConfigDataPacket(String fileName, byte[] fileData, Context context) {
|
||||||
|
this.fileName = fileName;
|
||||||
|
this.fileData = fileData;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ConfigDataPacket read(ByteBuf input, Context context, boolean FML3) {
|
||||||
|
String registryName = ProtocolUtils.readString(input);
|
||||||
|
byte[] snapshot = ProtocolUtils.readByteArray(input);
|
||||||
|
|
||||||
|
return new ConfigDataPacket(registryName, snapshot, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf encode() {
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, 4);
|
||||||
|
|
||||||
|
ProtocolUtils.writeString(buf, fileName);
|
||||||
|
ProtocolUtils.writeByteArray(buf, fileData);
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,46 @@
|
||||||
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
public class Context {
|
||||||
|
|
||||||
|
private final int responseID;
|
||||||
|
|
||||||
|
private final String channelName;
|
||||||
|
|
||||||
|
private Context(int responseID, String channelName) {
|
||||||
|
this.responseID = responseID;
|
||||||
|
this.channelName = channelName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Context createContext(int responseID, String channelName) {
|
||||||
|
return new Context(responseID, channelName);
|
||||||
|
}
|
||||||
|
|
||||||
|
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, 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, String channelName) {
|
||||||
|
super(responseID, channelName);
|
||||||
|
this.clientSuccess = clientSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean success() {
|
||||||
|
return clientSuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,24 +1,39 @@
|
||||||
package org.adde0109.ambassador.forge.packet;
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
|
||||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
public class GenericForgeLoginWrapperPacket extends DeferredByteBufHolder implements IForgeLoginWrapperPacket {
|
public class GenericForgeLoginWrapperPacket<T extends Context> implements IForgeLoginWrapperPacket<T> {
|
||||||
private final int id;
|
|
||||||
|
|
||||||
public GenericForgeLoginWrapperPacket(ByteBuf input, int id) {
|
private final byte[] content;
|
||||||
super(input);
|
private final T context;
|
||||||
this.id = id;
|
|
||||||
|
public GenericForgeLoginWrapperPacket(byte[] content, T context) {
|
||||||
|
this.content = content;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static public GenericForgeLoginWrapperPacket<?> read(ByteBuf input, Context context) {
|
||||||
|
byte[] content = new byte[input.readableBytes()];
|
||||||
|
input.readBytes(content);
|
||||||
|
return new GenericForgeLoginWrapperPacket<>(content, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginPluginResponse encode() {
|
public ByteBuf encode() {
|
||||||
return new LoginPluginResponse(id, true, content());
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
buf.writeBytes(content);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getContent() {
|
||||||
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId() {
|
public T getContext() {
|
||||||
return id;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
package org.adde0109.ambassador.forge.packet;
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
|
||||||
public interface IForgeLoginWrapperPacket {
|
public interface IForgeLoginWrapperPacket<T extends Context> {
|
||||||
public LoginPluginResponse encode();
|
ByteBuf encode();
|
||||||
public int getId();
|
T getContext();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +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 implements IForgeLoginWrapperPacket<Context> {
|
||||||
|
private final byte[] content;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
ModDataPacket(byte[] content, Context 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||||
|
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class ModListPacket implements IForgeLoginWrapperPacket<Context> {
|
||||||
|
private List<String> mods;
|
||||||
|
private Map<ChannelIdentifier, String> channels;
|
||||||
|
private List<String> registries;
|
||||||
|
private final List<String> dataPackRegistries;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
private ModListPacket(List<String> mods, Map<ChannelIdentifier,
|
||||||
|
String> channels, List<String> registries, Context context, List<String> dataPackRegistries) {
|
||||||
|
this.mods = mods;
|
||||||
|
this.channels = channels;
|
||||||
|
this.registries = registries;
|
||||||
|
this.context = context;
|
||||||
|
this.dataPackRegistries = dataPackRegistries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ModListPacket read(ByteBuf input, Context context, boolean FML3) {
|
||||||
|
|
||||||
|
List<String> mods = new ArrayList<>();
|
||||||
|
int len = ProtocolUtils.readVarInt(input);
|
||||||
|
for (int x = 0; x < len; x++)
|
||||||
|
mods.add(ProtocolUtils.readString(input, 0x100));
|
||||||
|
|
||||||
|
Map<ChannelIdentifier, String> channels = new HashMap<>();
|
||||||
|
len = ProtocolUtils.readVarInt(input);
|
||||||
|
for (int x = 0; x < len; x++)
|
||||||
|
channels.put(MinecraftChannelIdentifier.from(ProtocolUtils.readString(input, 32767)),
|
||||||
|
ProtocolUtils.readString(input, 0x100));
|
||||||
|
|
||||||
|
List<String> registries = new ArrayList<>();
|
||||||
|
len = ProtocolUtils.readVarInt(input);
|
||||||
|
for (int x = 0; x < len; x++)
|
||||||
|
registries.add(ProtocolUtils.readString(input, 32767));
|
||||||
|
|
||||||
|
List<String> dataPackRegistries = null;
|
||||||
|
if (FML3 && input.isReadable()) {
|
||||||
|
dataPackRegistries = new ArrayList<>();
|
||||||
|
len = ProtocolUtils.readVarInt(input);
|
||||||
|
for (int x = 0; x < len; x++)
|
||||||
|
dataPackRegistries.add(ProtocolUtils.readString(input, 0x100));
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ModListPacket(mods, channels, registries, context, dataPackRegistries);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf encode() {
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, 1);
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, mods.size());
|
||||||
|
mods.forEach(m -> ProtocolUtils.writeString(buf, m));
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, channels.size());
|
||||||
|
channels.forEach((k, v) -> {
|
||||||
|
ProtocolUtils.writeString(buf,k.getId());
|
||||||
|
ProtocolUtils.writeString(buf,v);
|
||||||
|
});
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, registries.size());
|
||||||
|
registries.forEach(k -> ProtocolUtils.writeString(buf, k));
|
||||||
|
|
||||||
|
if (dataPackRegistries != null) {
|
||||||
|
ProtocolUtils.writeVarInt(buf, dataPackRegistries.size());
|
||||||
|
dataPackRegistries.forEach(k -> ProtocolUtils.writeString(buf, k));
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getMods() {
|
||||||
|
return mods;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<ChannelIdentifier, String> getChannels() {
|
||||||
|
return channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getRegistries() {
|
||||||
|
return registries;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
||||||
|
|
@ -12,24 +11,22 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ModListReplyPacket implements IForgeLoginWrapperPacket {
|
public class ModListReplyPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
|
||||||
|
|
||||||
private List<String> mods;
|
private List<String> mods;
|
||||||
private Map<ChannelIdentifier, String> channels;
|
private Map<ChannelIdentifier, String> channels;
|
||||||
private Map<String, String> registries;
|
private Map<String, String> registries;
|
||||||
|
|
||||||
private final int id;
|
private final Context.ClientContext context;
|
||||||
|
|
||||||
private ModListReplyPacket(List<String> mods, Map<ChannelIdentifier,
|
private ModListReplyPacket(List<String> mods, Map<ChannelIdentifier,
|
||||||
String> channels, Map<String, String> registries, int id) {
|
String> channels, Map<String, String> registries, Context.ClientContext context) {
|
||||||
this.mods = mods;
|
this.mods = mods;
|
||||||
this.channels = channels;
|
this.channels = channels;
|
||||||
this.registries = registries;
|
this.registries = registries;
|
||||||
this.id = id;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ModListReplyPacket read(LoginPluginResponse msg) {
|
public static ModListReplyPacket read(ByteBuf input, Context.ClientContext context) {
|
||||||
ByteBuf input = msg.content();
|
|
||||||
|
|
||||||
List<String> mods = new ArrayList<>();
|
List<String> mods = new ArrayList<>();
|
||||||
int len = ProtocolUtils.readVarInt(input);
|
int len = ProtocolUtils.readVarInt(input);
|
||||||
|
|
@ -47,11 +44,11 @@ public class ModListReplyPacket implements IForgeLoginWrapperPacket {
|
||||||
for (int x = 0; x < len; x++)
|
for (int x = 0; x < len; x++)
|
||||||
registries.put(ProtocolUtils.readString(input, 32767), ProtocolUtils.readString(input, 0x100));
|
registries.put(ProtocolUtils.readString(input, 32767), ProtocolUtils.readString(input, 0x100));
|
||||||
|
|
||||||
return new ModListReplyPacket(mods, channels, registries, msg.getId());
|
return new ModListReplyPacket(mods, channels, registries, context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public LoginPluginResponse encode() {
|
public ByteBuf encode() {
|
||||||
ByteBuf buf = Unpooled.buffer();
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
ProtocolUtils.writeVarInt(buf, 2);
|
ProtocolUtils.writeVarInt(buf, 2);
|
||||||
|
|
@ -71,17 +68,12 @@ public class ModListReplyPacket implements IForgeLoginWrapperPacket {
|
||||||
ProtocolUtils.writeString(buf, v);
|
ProtocolUtils.writeString(buf, v);
|
||||||
});
|
});
|
||||||
|
|
||||||
ByteBuf output = Unpooled.buffer();
|
return buf;
|
||||||
ProtocolUtils.writeString(output, "fml:handshake");
|
|
||||||
ProtocolUtils.writeVarInt(output, buf.readableBytes());
|
|
||||||
output.writeBytes(buf);
|
|
||||||
|
|
||||||
return new LoginPluginResponse(id,true,output);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getId() {
|
public Context.ClientContext getContext() {
|
||||||
return id;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getMods() {
|
public List<String> getMods() {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
|
public class RegistryPacket implements IForgeLoginWrapperPacket<Context> {
|
||||||
|
|
||||||
|
private final String registryName;
|
||||||
|
|
||||||
|
private final byte[] snapshot;
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
public RegistryPacket(String registryName, byte[] snapshot, Context context) {
|
||||||
|
this.registryName = registryName;
|
||||||
|
this.snapshot = snapshot;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RegistryPacket read(ByteBuf input, Context context, boolean FML3) {
|
||||||
|
String registryName = ProtocolUtils.readString(input);
|
||||||
|
byte[] snapshot = null;
|
||||||
|
if (input.readBoolean()) {
|
||||||
|
snapshot = new byte[input.readableBytes()];
|
||||||
|
input.readBytes(snapshot);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new RegistryPacket(registryName, snapshot, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf encode() {
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
|
ProtocolUtils.writeVarInt(buf, 3);
|
||||||
|
|
||||||
|
ProtocolUtils.writeString(buf, registryName);
|
||||||
|
if (snapshot != null) {
|
||||||
|
buf.writeBoolean(true);
|
||||||
|
buf.writeBytes(snapshot);
|
||||||
|
} else {
|
||||||
|
buf.writeBoolean(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getRegistryName() {
|
||||||
|
return registryName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getSnapshot() {
|
||||||
|
return snapshot;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
package org.adde0109.ambassador.forge.pipeline;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponsePacket;
|
||||||
|
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
||||||
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
|
import io.netty.handler.codec.DecoderException;
|
||||||
|
import io.netty.handler.codec.MessageToMessageCodec;
|
||||||
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import org.adde0109.ambassador.Ambassador;
|
||||||
|
import org.adde0109.ambassador.forge.packet.*;
|
||||||
|
|
||||||
|
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 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 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();
|
||||||
|
|
||||||
|
if (!context.getChannelName().equals("fml:handshake")) {
|
||||||
|
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
||||||
|
} 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 {
|
||||||
|
switch (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) {
|
||||||
|
out.add(ModDataPacket.read(buf, context));
|
||||||
|
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 server on fml:handshake: " + packetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
|
||||||
|
ByteBuf wrapped;
|
||||||
|
|
||||||
|
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)) { //ModDataPacket doesn't require a response
|
||||||
|
this.loginWrapperContexts.put(msg.getContext().getResponseID(), msg.getContext());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
package org.adde0109.ambassador.forge.pipeline;
|
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
|
||||||
import io.netty.buffer.ByteBuf;
|
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
|
||||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
|
||||||
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
|
||||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
public class ForgeLoginWrapperDecoder extends MessageToMessageDecoder<LoginPluginResponse> {
|
|
||||||
|
|
||||||
private final List<Integer> loginWrapperIDs = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void decode(ChannelHandlerContext ctx, LoginPluginResponse msg, List<Object> out) throws Exception {
|
|
||||||
ByteBuf buf = msg.content();
|
|
||||||
if (!loginWrapperIDs.remove((Integer) msg.getId())) {
|
|
||||||
out.add(msg.retain());
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int originalReaderIndex = msg.content().readerIndex();
|
|
||||||
String channel = ProtocolUtils.readString(buf);
|
|
||||||
if (!channel.equals("fml:handshake")) {
|
|
||||||
buf.readerIndex(originalReaderIndex);
|
|
||||||
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId()));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int length = ProtocolUtils.readVarInt(buf);
|
|
||||||
int packetID = ProtocolUtils.readVarInt(buf);
|
|
||||||
if (packetID == 2) {
|
|
||||||
out.add(ModListReplyPacket.read(msg));
|
|
||||||
} else {
|
|
||||||
buf.readerIndex(originalReaderIndex);
|
|
||||||
out.add(new GenericForgeLoginWrapperPacket(buf.retain(), msg.getId()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void registerLoginWrapperID(int loginWrapperID) {
|
|
||||||
this.loginWrapperIDs.add(loginWrapperID);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,25 +1,42 @@
|
||||||
package org.adde0109.ambassador.forge.pipeline;
|
package org.adde0109.ambassador.forge.pipeline;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||||
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.SimpleChannelInboundHandler;
|
import io.netty.channel.SimpleChannelInboundHandler;
|
||||||
import io.netty.util.ReferenceCountUtil;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||||
|
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
||||||
|
import org.adde0109.ambassador.forge.VelocityForgeBackendConnectionPhase;
|
||||||
|
import org.adde0109.ambassador.forge.packet.Context;
|
||||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
||||||
|
|
||||||
public class ForgeLoginWrapperHandler extends SimpleChannelInboundHandler<IForgeLoginWrapperPacket> {
|
public class ForgeLoginWrapperHandler extends SimpleChannelInboundHandler<IForgeLoginWrapperPacket<?>> {
|
||||||
|
|
||||||
private final ConnectedPlayer player;
|
private final MinecraftConnectionAssociation connection;
|
||||||
|
|
||||||
|
|
||||||
public ForgeLoginWrapperHandler(ConnectedPlayer player) {
|
public ForgeLoginWrapperHandler(MinecraftConnectionAssociation connection) {
|
||||||
super(false);
|
super(false);
|
||||||
this.player = player;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void channelRead0(ChannelHandlerContext ctx, IForgeLoginWrapperPacket msg) throws Exception {
|
protected void channelRead0(ChannelHandlerContext ctx, IForgeLoginWrapperPacket msg) throws Exception {
|
||||||
|
if (connection instanceof ConnectedPlayer player) {
|
||||||
VelocityForgeClientConnectionPhase phase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
VelocityForgeClientConnectionPhase phase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
phase.handle(player,msg,player.getConnectionInFlight());
|
phase.handle(player,msg,player.getConnectionInFlight());
|
||||||
|
} else if (connection instanceof VelocityServerConnection serverConnection){
|
||||||
|
if (!(serverConnection.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||||
|
serverConnection.getConnection().setType(serverConnection.getPlayer().getConnection().getType());
|
||||||
|
serverConnection.setConnectionPhase(serverConnection.getConnection().getType().getInitialBackendPhase());
|
||||||
|
}
|
||||||
|
|
||||||
|
VelocityForgeBackendConnectionPhase phase =
|
||||||
|
(VelocityForgeBackendConnectionPhase) serverConnection.getPhase();
|
||||||
|
phase.handle(serverConnection, serverConnection.getPlayer(), msg);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,36 +7,34 @@ import io.netty.channel.ChannelInitializer;
|
||||||
import org.adde0109.ambassador.forge.ForgeConstants;
|
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||||
import org.adde0109.ambassador.velocity.backend.FMLMarkerAdder;
|
import org.adde0109.ambassador.velocity.backend.FMLMarkerAdder;
|
||||||
import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendHandshakeHandler;
|
import org.adde0109.ambassador.velocity.backend.VelocityForgeBackendHandshakeHandler;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class VelocityBackendChannelInitializer extends BackendChannelInitializer {
|
public class VelocityBackendChannelInitializer extends BackendChannelInitializer {
|
||||||
|
|
||||||
private static final Method INIT_CHANNEL;
|
private final Method INIT_CHANNEL;
|
||||||
|
|
||||||
private final ChannelInitializer<?> delegate;
|
private final ChannelInitializer<Channel> delegate;
|
||||||
private final VelocityServer server;
|
private final VelocityServer server;
|
||||||
|
|
||||||
static {
|
public VelocityBackendChannelInitializer(ChannelInitializer<Channel> delegate, VelocityServer server) {
|
||||||
|
super(server);
|
||||||
|
this.delegate = delegate;
|
||||||
|
this.server = server;
|
||||||
try {
|
try {
|
||||||
INIT_CHANNEL = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
|
INIT_CHANNEL = delegate.getClass().getDeclaredMethod("initChannel", Channel.class);
|
||||||
INIT_CHANNEL.setAccessible(true);
|
INIT_CHANNEL.setAccessible(true);
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public VelocityBackendChannelInitializer(ChannelInitializer<?> delegate, VelocityServer server) {
|
|
||||||
super(server);
|
|
||||||
this.delegate = delegate;
|
|
||||||
this.server = server;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void initChannel(Channel ch) {
|
protected void initChannel(Channel ch) {
|
||||||
try {
|
try {
|
||||||
INIT_CHANNEL.invoke(delegate,ch);
|
INIT_CHANNEL.invoke(delegate, ch);
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,19 +3,19 @@ package org.adde0109.ambassador.velocity;
|
||||||
import com.velocitypowered.api.event.Continuation;
|
import com.velocitypowered.api.event.Continuation;
|
||||||
import com.velocitypowered.api.event.PostOrder;
|
import com.velocitypowered.api.event.PostOrder;
|
||||||
import com.velocitypowered.api.event.Subscribe;
|
import com.velocitypowered.api.event.Subscribe;
|
||||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
|
||||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||||
import com.velocitypowered.api.event.player.*;
|
import com.velocitypowered.api.event.player.*;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
|
import com.velocitypowered.api.util.ModInfo;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
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.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import net.kyori.adventure.text.Component;
|
|
||||||
import org.adde0109.ambassador.Ambassador;
|
import org.adde0109.ambassador.Ambassador;
|
||||||
import org.adde0109.ambassador.forge.ForgeConstants;
|
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||||
|
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
||||||
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
||||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperDecoder;
|
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperCodec;
|
||||||
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperHandler;
|
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperHandler;
|
||||||
|
|
||||||
public class VelocityEventHandler {
|
public class VelocityEventHandler {
|
||||||
|
|
@ -26,28 +26,25 @@ public class VelocityEventHandler {
|
||||||
this.ambassador = ambassador;
|
this.ambassador = ambassador;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe
|
@Subscribe(order = PostOrder.FIRST)
|
||||||
public void onLoginEvent(LoginEvent event, Continuation continuation) {
|
public void onPostLoginEvent(PostLoginEvent event, Continuation continuation) {
|
||||||
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
|
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
|
||||||
if (player.getPhase() instanceof VelocityForgeClientConnectionPhase) {
|
if (player.getPhase() instanceof VelocityForgeClientConnectionPhase) {
|
||||||
|
((VelocityServer) Ambassador.getInstance().server).unregisterConnection(player);
|
||||||
|
|
||||||
player.getConnection().eventLoop().submit(() -> {
|
player.getConnection().eventLoop().submit(() -> {
|
||||||
player.getConnection().setState(StateRegistry.LOGIN);
|
player.getConnection().setState(StateRegistry.LOGIN);
|
||||||
|
|
||||||
player.getConnection().getChannel().pipeline().addBefore(
|
player.getConnection().getChannel().pipeline().addBefore(
|
||||||
Connections.HANDLER,
|
Connections.HANDLER,
|
||||||
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperDecoder());
|
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperCodec(
|
||||||
|
player.getConnection().getType() == ForgeConstants.ForgeFML3));
|
||||||
player.getConnection().getChannel().pipeline().addAfter(
|
player.getConnection().getChannel().pipeline().addAfter(
|
||||||
ForgeConstants.FORGE_HANDSHAKE_DECODER,
|
ForgeConstants.FORGE_HANDSHAKE_DECODER,
|
||||||
ForgeConstants.FORGE_HANDSHAKE_HANDLER, new ForgeLoginWrapperHandler(player));
|
ForgeConstants.FORGE_HANDSHAKE_HANDLER, new ForgeLoginWrapperHandler(player));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
continuation.resume();
|
//event.getPlayer().sendMessage(Component.text("post login event"));
|
||||||
}
|
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.FIRST)
|
|
||||||
public void onPostLoginEvent(PostLoginEvent event, Continuation continuation) {
|
|
||||||
if (((ConnectedPlayer) event.getPlayer()).getPhase() instanceof VelocityForgeClientConnectionPhase)
|
|
||||||
((VelocityServer) Ambassador.getInstance().server).unregisterConnection((ConnectedPlayer) event.getPlayer());
|
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +58,25 @@ public class VelocityEventHandler {
|
||||||
RegisteredServer chosenServer = Ambassador.getTemporaryForced().remove(player.getUsername());
|
RegisteredServer chosenServer = Ambassador.getTemporaryForced().remove(player.getUsername());
|
||||||
if (chosenServer != null)
|
if (chosenServer != null)
|
||||||
event.setInitialServer(chosenServer);
|
event.setInitialServer(chosenServer);
|
||||||
|
//event.getPlayer().sendMessage(Component.text("choose server event"));
|
||||||
continuation.resume();
|
continuation.resume();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe
|
||||||
|
public void onPlayerChannelRegisterEvent(PlayerChannelRegisterEvent event) {
|
||||||
|
ConnectedPlayer player = (ConnectedPlayer) event.getPlayer();
|
||||||
|
if (!(player.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
player.setModInfo(new ModInfo("Channels", event.getChannels().stream().map((id) -> {
|
||||||
|
return new ModInfo.Mod(id.getId(), "");
|
||||||
|
}).toList()));
|
||||||
|
|
||||||
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
|
//If reset typ is still unknown, set it!
|
||||||
|
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.UNKNOWN) {
|
||||||
|
clientPhase.updateResetType(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||||
import com.velocitypowered.proxy.network.Connections;
|
import com.velocitypowered.proxy.network.Connections;
|
||||||
import com.velocitypowered.proxy.network.ServerChannelInitializer;
|
import com.velocitypowered.proxy.network.ServerChannelInitializer;
|
||||||
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelHandler;
|
import io.netty.channel.ChannelHandler;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
|
@ -14,22 +15,21 @@ import org.jetbrains.annotations.NotNull;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
|
|
||||||
public class VelocityServerChannelInitializer extends ServerChannelInitializer {
|
public class VelocityServerChannelInitializer extends ServerChannelInitializer {
|
||||||
private static final Method INIT_CHANNEL;
|
private Method INIT_CHANNEL;
|
||||||
|
|
||||||
private final ChannelInitializer<?> delegate;
|
private final ChannelInitializer<?> delegate;
|
||||||
|
private final VelocityServer server;
|
||||||
static {
|
|
||||||
try {
|
|
||||||
INIT_CHANNEL = ChannelInitializer.class.getDeclaredMethod("initChannel", Channel.class);
|
|
||||||
INIT_CHANNEL.setAccessible(true);
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public VelocityServerChannelInitializer(ChannelInitializer<?> delegate,VelocityServer server) {
|
public VelocityServerChannelInitializer(ChannelInitializer<?> delegate,VelocityServer server) {
|
||||||
super(server);
|
super(server);
|
||||||
this.delegate = delegate;
|
this.delegate = delegate;
|
||||||
|
this.server = server;
|
||||||
|
try {
|
||||||
|
INIT_CHANNEL = delegate.getClass().getDeclaredMethod("initChannel", Channel.class);
|
||||||
|
INIT_CHANNEL.setAccessible(true);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -39,9 +39,12 @@ public class VelocityServerChannelInitializer extends ServerChannelInitializer {
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
ChannelHandler handler = ch.pipeline().get(Connections.HANDLER);
|
finally {
|
||||||
if (!(handler instanceof final MinecraftConnection connection)) throw new RuntimeException("plugin conflict");
|
if (ch.pipeline().get(MinecraftConnection.class) == null)
|
||||||
HandshakeSessionHandler originalSessionHandler = (HandshakeSessionHandler) connection.getSessionHandler();
|
super.initChannel(ch);
|
||||||
connection.setSessionHandler(new VelocityHandshakeSessionHandler(originalSessionHandler, connection));
|
MinecraftConnection handler = ch.pipeline().get(MinecraftConnection.class);
|
||||||
|
HandshakeSessionHandler originalSessionHandler = (HandshakeSessionHandler) handler.getActiveSessionHandler();
|
||||||
|
handler.setActiveSessionHandler(StateRegistry.HANDSHAKE, new VelocityHandshakeSessionHandler(originalSessionHandler, handler, server));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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,21 +16,24 @@ 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();
|
||||||
|
|
||||||
if (serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType FMLType && server.getConfiguration().getPlayerInfoForwardingMode() != PlayerInfoForwarding.LEGACY) {
|
if (serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType FMLType
|
||||||
|
&& forwardingMode != PlayerInfoForwarding.LEGACY
|
||||||
|
&& forwardingMode != PlayerInfoForwarding.BUNGEEGUARD) {
|
||||||
msg.setServerAddress(msg.getServerAddress() + (FMLType == ForgeConstants.ForgeFML3 ? ForgeConstants.FML3Marker : ForgeConstants.FML2Marker));
|
msg.setServerAddress(msg.getServerAddress() + (FMLType == ForgeConstants.ForgeFML3 ? ForgeConstants.FML3Marker : ForgeConstants.FML2Marker));
|
||||||
}
|
}
|
||||||
out.add(msg);
|
out.add(msg);
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,17 @@
|
||||||
package org.adde0109.ambassador.velocity.backend;
|
package org.adde0109.ambassador.velocity.backend;
|
||||||
|
|
||||||
import com.velocitypowered.api.network.ProtocolVersion;
|
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
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.StateRegistry;
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessagePacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccessPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
|
||||||
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
import com.velocitypowered.proxy.util.except.QuietRuntimeException;
|
||||||
import net.kyori.adventure.text.Component;
|
import io.netty.buffer.Unpooled;
|
||||||
import org.adde0109.ambassador.Ambassador;
|
import org.adde0109.ambassador.forge.*;
|
||||||
import org.adde0109.ambassador.forge.ForgeConstants;
|
|
||||||
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
|
||||||
import org.adde0109.ambassador.forge.VelocityForgeBackendConnectionPhase;
|
|
||||||
import org.adde0109.ambassador.forge.VelocityForgeClientConnectionPhase;
|
|
||||||
import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
|
||||||
|
|
||||||
public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
|
public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
|
||||||
|
|
||||||
|
|
@ -34,77 +26,33 @@ public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(LoginPluginMessage packet) {
|
public boolean handle(ServerLoginSuccessPacket packet) {
|
||||||
if (packet.getChannel().equals("fml:loginwrapper")) {
|
|
||||||
if (serverConnection.getPhase() == BackendConnectionPhases.UNKNOWN) {
|
|
||||||
VelocityForgeBackendConnectionPhase.NOT_STARTED.handle(serverConnection,serverConnection.getPlayer(),packet);
|
|
||||||
} else if (serverConnection.getPhase() instanceof VelocityForgeBackendConnectionPhase phase1) {
|
|
||||||
phase1.handle(serverConnection,serverConnection.getPlayer(),packet);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return original.handle(packet);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean handle(ServerLoginSuccess 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); //Can lead to disconnect.
|
||||||
|
|
||||||
|
//If we are still connected after handling that package.
|
||||||
|
if (serverConnection.getConnection() != null) {
|
||||||
ConnectedPlayer player = serverConnection.getPlayer();
|
ConnectedPlayer player = serverConnection.getPlayer();
|
||||||
if (player.getConnectedServer() != null && player.getConnectedServer().getConnection().getType() instanceof ForgeFMLConnectionType) {
|
|
||||||
//Forge -> vanilla
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
//Has not already been reset
|
clientPhase.complete(player);
|
||||||
//Not for Vanilla -> Vanilla
|
|
||||||
player.getPhase().resetConnectionPhase(player);
|
|
||||||
} else if (player.getConnection().getState() == StateRegistry.LOGIN) {
|
|
||||||
//Initial vanilla
|
|
||||||
//Vanilla -> Forge
|
|
||||||
//Forge -> Forge
|
|
||||||
MinecraftConnection connection = player.getConnection();
|
|
||||||
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
|
|
||||||
.sendPacket();
|
|
||||||
connection.setState(StateRegistry.PLAY);
|
|
||||||
connection.getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
|
|
||||||
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
original.handle(packet);
|
|
||||||
|
|
||||||
serverConnection.getConnection().setSessionHandler(
|
|
||||||
new ForgePlaySessionHandler((TransitionSessionHandler) serverConnection
|
|
||||||
.getConnection().getSessionHandler(),serverConnection));
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean handle(Disconnect packet) {
|
public boolean handle(DisconnectPacket packet) {
|
||||||
if (!serverConnection.getPhase().consideredComplete()) {
|
|
||||||
serverConnection.getPlayer().handleConnectionException(serverConnection.getServer(), packet, false);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return original.handle(packet);
|
return original.handle(packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void disconnected() {
|
public void disconnected() {
|
||||||
//Same as default just not safe.
|
|
||||||
if (!serverConnection.getPhase().consideredComplete()) {
|
|
||||||
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
|
|
||||||
serverConnection.getPlayer().handleConnectionException(serverConnection.getServer(),
|
|
||||||
new QuietRuntimeException("The connection to the remote server was unexpectedly closed.\n"
|
|
||||||
+ "This is usually because the remote server does not have BungeeCord IP forwarding "
|
|
||||||
+ "correctly enabled.\nSee https://velocitypowered.com/wiki/users/forwarding/ "
|
|
||||||
+ "for instructions on how to configure player info forwarding correctly."),
|
|
||||||
false);
|
|
||||||
} else {
|
|
||||||
serverConnection.getPlayer().handleConnectionException(serverConnection.getServer(),
|
|
||||||
new QuietRuntimeException("The connection to the remote server was unexpectedly closed."),
|
|
||||||
false);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
original.disconnected();
|
original.disconnected();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.COMPLETE);
|
|
||||||
}
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -4,9 +4,14 @@ import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.backend.LoginSessionHandler;
|
import com.velocitypowered.proxy.connection.backend.LoginSessionHandler;
|
||||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.network.Connections;
|
import com.velocitypowered.proxy.network.Connections;
|
||||||
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
import io.netty.channel.*;
|
import io.netty.channel.*;
|
||||||
|
import org.adde0109.ambassador.forge.ForgeConstants;
|
||||||
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
import org.adde0109.ambassador.forge.ForgeFMLConnectionType;
|
||||||
|
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperCodec;
|
||||||
|
import org.adde0109.ambassador.forge.pipeline.ForgeLoginWrapperHandler;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter {
|
public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerAdapter {
|
||||||
|
|
@ -24,8 +29,18 @@ public class VelocityForgeBackendHandshakeHandler extends ChannelInboundHandlerA
|
||||||
|
|
||||||
ctx.pipeline().remove(this);
|
ctx.pipeline().remove(this);
|
||||||
|
|
||||||
if (serverConnection.getPlayer().getConnection().getType() instanceof ForgeFMLConnectionType) {
|
ConnectedPlayer player = serverConnection.getPlayer();
|
||||||
connection.setSessionHandler(new ForgeLoginSessionHandler((LoginSessionHandler) connection.getSessionHandler(),serverConnection,server));
|
if (player.getConnection().getType() instanceof ForgeFMLConnectionType) {
|
||||||
|
ForgeLoginSessionHandler forgeLoginSessionHandler = new ForgeLoginSessionHandler((LoginSessionHandler) connection.getActiveSessionHandler(), serverConnection,server);
|
||||||
|
connection.setActiveSessionHandler(StateRegistry.LOGIN, forgeLoginSessionHandler);
|
||||||
|
|
||||||
|
serverConnection.getConnection().getChannel().pipeline().addBefore(
|
||||||
|
Connections.HANDLER,
|
||||||
|
ForgeConstants.FORGE_HANDSHAKE_DECODER, new ForgeLoginWrapperCodec(
|
||||||
|
player.getConnection().getType() == ForgeConstants.ForgeFML3));
|
||||||
|
serverConnection.getConnection().getChannel().pipeline().addAfter(
|
||||||
|
ForgeConstants.FORGE_HANDSHAKE_DECODER,
|
||||||
|
ForgeConstants.FORGE_HANDSHAKE_HANDLER, new ForgeLoginWrapperHandler(serverConnection));
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.pipeline().fireChannelActive();
|
ctx.pipeline().fireChannelActive();
|
||||||
|
|
|
||||||
|
|
@ -4,16 +4,17 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
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.MinecraftEncoder;
|
|
||||||
import com.velocitypowered.proxy.protocol.util.DeferredByteBufHolder;
|
|
||||||
import io.netty.channel.*;
|
import io.netty.channel.*;
|
||||||
import io.netty.handler.codec.EncoderException;
|
|
||||||
import io.netty.util.ReferenceCountUtil;
|
|
||||||
|
|
||||||
|
|
||||||
public class PluginLoginPacketQueue extends ChannelOutboundHandlerAdapter {
|
public class ClientPacketQueue extends ChannelOutboundHandlerAdapter {
|
||||||
|
|
||||||
private PendingWriteQueue queue;
|
private PendingWriteQueue queue;
|
||||||
|
private final StateRegistry allow;
|
||||||
|
|
||||||
|
public ClientPacketQueue(StateRegistry registry) {
|
||||||
|
this.allow = registry;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
||||||
|
|
@ -23,9 +24,9 @@ public class PluginLoginPacketQueue 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 {
|
||||||
MinecraftConnection connection = ctx.pipeline().get(MinecraftConnection.class);
|
MinecraftConnection connection = ctx.pipeline().get(MinecraftConnection.class);
|
||||||
if (connection.getState() == StateRegistry.LOGIN && msg instanceof MinecraftPacket packet) {
|
if (msg instanceof MinecraftPacket packet) {
|
||||||
try {
|
try {
|
||||||
StateRegistry.LOGIN.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND ,
|
allow.getProtocolRegistry(ProtocolUtils.Direction.CLIENTBOUND ,
|
||||||
connection.getProtocolVersion()).getPacketId(packet);
|
connection.getProtocolVersion()).getPacketId(packet);
|
||||||
ctx.write(msg,promise);
|
ctx.write(msg,promise);
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
package org.adde0109.ambassador.velocity.client;
|
package org.adde0109.ambassador.velocity.client;
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
|
||||||
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.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.channel.ChannelInboundHandlerAdapter;
|
import io.netty.channel.ChannelInboundHandlerAdapter;
|
||||||
|
import org.adde0109.ambassador.forge.packet.Context;
|
||||||
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||||
|
|
||||||
|
|
@ -27,8 +27,8 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter {
|
||||||
boolean success = buf.readBoolean();
|
boolean success = buf.readBoolean();
|
||||||
if (id == 98) {
|
if (id == 98) {
|
||||||
try {
|
try {
|
||||||
IForgeLoginWrapperPacket packet = new GenericForgeLoginWrapperPacket(buf, id);
|
ctx.fireChannelRead(GenericForgeLoginWrapperPacket.read(
|
||||||
ctx.fireChannelRead(packet);
|
Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success, "fml:handshake")));
|
||||||
} finally {
|
} finally {
|
||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,33 +0,0 @@
|
||||||
package org.adde0109.ambassador.velocity.client;
|
|
||||||
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
|
||||||
import io.netty.channel.*;
|
|
||||||
|
|
||||||
public class OutboundForgeHandshakeQueue extends ChannelOutboundHandlerAdapter {
|
|
||||||
|
|
||||||
PendingWriteQueue writeQueue;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
writeQueue = new PendingWriteQueue(ctx);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
|
|
||||||
if ((msg instanceof LoginPluginMessage packet)) {
|
|
||||||
writeQueue.add(msg, promise);
|
|
||||||
} else {
|
|
||||||
ctx.write(msg, promise);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
|
|
||||||
if (ctx.channel().isActive()) {
|
|
||||||
writeQueue.removeAndWriteAll();
|
|
||||||
ctx.flush();
|
|
||||||
} else {
|
|
||||||
writeQueue.removeAndFailAll(new ChannelException());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -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);
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,42 @@
|
||||||
package org.adde0109.ambassador.velocity.client;
|
package org.adde0109.ambassador.velocity.client;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.ConnectionTypes;
|
import com.velocitypowered.proxy.connection.ConnectionTypes;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
|
||||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
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;
|
||||||
|
|
||||||
public class VelocityHandshakeSessionHandler implements MinecraftSessionHandler {
|
public class VelocityHandshakeSessionHandler extends HandshakeSessionHandler {
|
||||||
private final HandshakeSessionHandler original;
|
private final HandshakeSessionHandler original;
|
||||||
private final MinecraftConnection connection;
|
private final MinecraftConnection connection;
|
||||||
|
|
||||||
public VelocityHandshakeSessionHandler(HandshakeSessionHandler original, MinecraftConnection connection) {
|
public VelocityHandshakeSessionHandler(HandshakeSessionHandler original, MinecraftConnection connection, VelocityServer server) {
|
||||||
|
super(connection, server);
|
||||||
this.original = original;
|
this.original = original;
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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");
|
||||||
if (connection.getState() == StateRegistry.LOGIN && markerSplit.length > 1) {
|
if (connection.getState() == StateRegistry.LOGIN && markerSplit.length > 1 && markerSplit[1].startsWith("FML")) {
|
||||||
switch (markerSplit[1]) {
|
switch (markerSplit[1]) {
|
||||||
case "FML2":
|
case "FML2":
|
||||||
connection.setType(ForgeConstants.ForgeFML2);
|
connection.setType(ForgeConstants.ForgeFML2);
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.SERVER_SUCCESS_LISTENER, new OutboundSuccessHolder());
|
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
|
|
||||||
break;
|
break;
|
||||||
case "FML3":
|
case "FML3":
|
||||||
connection.setType(ForgeConstants.ForgeFML3);
|
connection.setType(ForgeConstants.ForgeFML3);
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.SERVER_SUCCESS_LISTENER, new OutboundSuccessHolder());
|
|
||||||
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new PluginLoginPacketQueue());
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.SERVER_SUCCESS_LISTENER, new OutboundSuccessHolder());
|
||||||
|
connection.getChannel().pipeline().addAfter(Connections.MINECRAFT_ENCODER,ForgeConstants.PLUGIN_PACKET_QUEUE, new ClientPacketQueue(StateRegistry.LOGIN));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,29 @@
|
||||||
# Do not change this
|
# Do not change this
|
||||||
config-version = "1.1"
|
config-version = "2.1"
|
||||||
|
|
||||||
# How long to wait for the client to reset before disconnecting (In milliseconds)
|
|
||||||
reset-timeout = 3000
|
|
||||||
# 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 server’s 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>"
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user