Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbf12f90e7 | ||
|
|
b2a9eeffa1 | ||
|
|
eaa974593c | ||
|
|
0fcf6af72f | ||
|
|
c7c2e1cd33 | ||
|
|
9c824abd45 | ||
|
|
25c6152087 | ||
|
|
e6532b4e9d | ||
|
|
45e41a5b83 | ||
|
|
b91d767163 | ||
|
|
5ffea5bc96 | ||
|
|
3960efdba5 | ||
|
|
246b8131b3 | ||
|
|
4b838990a6 | ||
|
|
89372a4a3c | ||
|
|
34ef23593d | ||
|
|
8767d55873 | ||
|
|
cd6c0be56e | ||
|
|
6dfbdecfaf |
16
README.md
16
README.md
|
|
@ -3,7 +3,8 @@
|
||||||
This is a Velocity plugin that makes it possible to host a modern Forge server behind a Velocity proxy!
|
This is a Velocity plugin that makes it possible to host a modern Forge server behind a Velocity proxy!
|
||||||
|
|
||||||
Unlike other solutions, this plugin does not require any special modifications to the backend server nor the client. (The player doesn't need to do anything)
|
Unlike other solutions, this plugin does not require any special modifications to the backend server nor the client. (The player doesn't need to do anything)
|
||||||
|
## Only for 1.13-1.20.1
|
||||||
|
Velocity has now added built-in support for newer mc versions and therefore don't need Ambassador. You might still need PCF mod on the server-side, for more info please visit: https://github.com/adde0109/Proxy-Compatible-Forge
|
||||||
## How to get started:
|
## How to get started:
|
||||||
1. Download and install this plugin to your proxy.
|
1. Download and install this plugin to your proxy.
|
||||||
2. After starting the server, configure the plugin it to your liking using the config file found in the folder "Ambassador".
|
2. After starting the server, configure the plugin it to your liking using the config file found in the folder "Ambassador".
|
||||||
|
|
@ -13,12 +14,13 @@ Unlike other solutions, this plugin does not require any special modifications t
|
||||||
- https://github.com/caunt/BungeeForge (Legacy forwarding)
|
- https://github.com/caunt/BungeeForge (Legacy forwarding)
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
* Server switching using kick to reset the client with configureble message and switch timeout.
|
* Server switching without any client side mod when the servers are similar. (Mods must match)
|
||||||
* ServerRedirect support for auto-reconnecting during switch.
|
* ServerRedirect support for server switching.
|
||||||
* Server switching using client mod for instant server switching:
|
* Server switching using Client Reset Packet Mod for instant server switching:
|
||||||
https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
|
|
||||||
1.18.2 and 1.19 fork:
|
1.16.5: https://github.com/Just-Chaldea/Forge-Client-Reset-Packet
|
||||||
https://github.com/adde0109/Forge-Client-Reset-Packet
|
|
||||||
|
1.18.2+: https://www.curseforge.com/minecraft/mc-mods/forge-client-reset-packet-forward
|
||||||
|
|
||||||
## Stuck on "Negotiating":
|
## Stuck on "Negotiating":
|
||||||
This is an issue with Client Reset Packet Mod being partly incompatible with certain mods on the client. Please remove incompatible mods on the client if you have this issue. (Yes, this also includes client-side only mods.)
|
This is an issue with Client Reset Packet Mod being partly incompatible with certain mods on the client. Please remove incompatible mods on the client if you have this issue. (Yes, this also includes client-side only mods.)
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ plugins {
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "org.adde0109"
|
group = "org.adde0109"
|
||||||
version = "1.4.3-beta"
|
version = "1.5.3-beta"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
|
||||||
|
|
@ -19,14 +19,18 @@ import java.util.Map;
|
||||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||||
import com.velocitypowered.proxy.VelocityServer;
|
import com.velocitypowered.proxy.VelocityServer;
|
||||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||||
|
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||||
|
import com.velocitypowered.proxy.protocol.packet.DisconnectPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier;
|
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier;
|
||||||
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry;
|
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertyRegistry;
|
||||||
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer;
|
import com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentPropertySerializer;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.netty.channel.ChannelInitializer;
|
import io.netty.channel.ChannelInitializer;
|
||||||
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.minimessage.MiniMessage;
|
||||||
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
|
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
|
||||||
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
|
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
|
||||||
import org.adde0109.ambassador.velocity.VelocityEventHandler;
|
import org.adde0109.ambassador.velocity.VelocityEventHandler;
|
||||||
|
|
@ -42,11 +46,11 @@ import java.util.concurrent.TimeUnit;
|
||||||
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_19;
|
||||||
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
import static com.velocitypowered.proxy.protocol.packet.brigadier.ArgumentIdentifier.mapSet;
|
||||||
|
|
||||||
@Plugin(id = "ambassador", name = "Ambassador", version = "1.4.3-beta", authors = {"adde0109"})
|
@Plugin(id = "ambassador", name = "Ambassador", version = "1.5.3-beta", authors = {"adde0109"})
|
||||||
public class Ambassador {
|
public class Ambassador {
|
||||||
|
|
||||||
//Don't forget to update checkCompatibleVersion() when changing this value
|
//Don't forget to update checkCompatibleVersion() when changing this value
|
||||||
private static final String minVelocityVersion = "velocity-3.2.0-SNAPSHOT-266";
|
private static final String minVelocityVersion = "velocity-3.3.0-SNAPSHOT-330";
|
||||||
|
|
||||||
public ProxyServer server;
|
public ProxyServer server;
|
||||||
public final Logger logger;
|
public final Logger logger;
|
||||||
|
|
@ -75,11 +79,11 @@ public class Ambassador {
|
||||||
boolean checkCompatibleVersion() {
|
boolean checkCompatibleVersion() {
|
||||||
//Update this when changing minVelocityVersion
|
//Update this when changing minVelocityVersion
|
||||||
try {
|
try {
|
||||||
MinecraftConnection.class.getDeclaredMethod("setActiveSessionHandler", StateRegistry.class);
|
Class.forName("com.velocitypowered.proxy.protocol.packet.DisconnectPacket");
|
||||||
} catch (NoSuchMethodException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
return false;
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Subscribe(order = PostOrder.LAST)
|
@Subscribe(order = PostOrder.LAST)
|
||||||
|
|
@ -152,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);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,92 +14,119 @@ import java.nio.file.Path;
|
||||||
|
|
||||||
public class AmbassadorConfig {
|
public class AmbassadorConfig {
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private int serverSwitchCancellationTime = 30;
|
private int serverSwitchCancellationTime = 30;
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean silenceWarnings = false;
|
private boolean silenceWarnings = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassRegistryCheck = false;
|
||||||
|
@Expose
|
||||||
|
private boolean bypassModCheck = false;
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean bypassRegistryCheck = false;
|
private boolean debugMode = false;
|
||||||
@Expose
|
|
||||||
private boolean bypassModCheck = false;
|
|
||||||
|
|
||||||
@Expose
|
@Expose
|
||||||
private boolean debugMode = false;
|
private boolean enableKickReset = false;
|
||||||
|
|
||||||
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck, boolean debugMode) {
|
@Expose
|
||||||
this.silenceWarnings = silenceWarnings;
|
private String kickReconnectMessageString = "<red>Please reconnect.</red>";
|
||||||
this.bypassRegistryCheck = bypassRegistryCheck;
|
|
||||||
this.bypassModCheck = bypassModCheck;
|
|
||||||
this.debugMode = debugMode;
|
|
||||||
};
|
|
||||||
|
|
||||||
public static AmbassadorConfig read(Path path) throws IOException {
|
private AmbassadorConfig(boolean silenceWarnings, boolean bypassRegistryCheck, boolean bypassModCheck,
|
||||||
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
boolean debugMode, boolean enableKickReset, String kickReconnectMessageString) {
|
||||||
.getResource("default-ambassador.toml");
|
this.silenceWarnings = silenceWarnings;
|
||||||
if (defaultConfigLocation == null) {
|
this.bypassRegistryCheck = bypassRegistryCheck;
|
||||||
throw new RuntimeException("Default configuration file does not exist.");
|
this.bypassModCheck = bypassModCheck;
|
||||||
|
this.debugMode = debugMode;
|
||||||
|
this.enableKickReset = enableKickReset;
|
||||||
|
this.kickReconnectMessageString = kickReconnectMessageString;
|
||||||
}
|
}
|
||||||
|
|
||||||
CommentedFileConfig config = CommentedFileConfig.builder(path)
|
public static AmbassadorConfig read(Path path) throws IOException {
|
||||||
.defaultData(defaultConfigLocation)
|
URL defaultConfigLocation = AmbassadorConfig.class.getClassLoader()
|
||||||
.autosave()
|
.getResource("default-ambassador.toml");
|
||||||
.preserveInsertionOrder()
|
if (defaultConfigLocation == null) {
|
||||||
.sync()
|
throw new RuntimeException("Default configuration file does not exist.");
|
||||||
.build();
|
}
|
||||||
config.load();
|
|
||||||
|
|
||||||
double configVersion;
|
CommentedFileConfig config = CommentedFileConfig.builder(path)
|
||||||
try {
|
.defaultData(defaultConfigLocation)
|
||||||
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
|
.autosave()
|
||||||
} catch (NumberFormatException e) {
|
.preserveInsertionOrder()
|
||||||
configVersion = 1.0;
|
.sync()
|
||||||
|
.build();
|
||||||
|
config.load();
|
||||||
|
|
||||||
|
double configVersion;
|
||||||
|
try {
|
||||||
|
configVersion = Double.parseDouble(config.getOrElse("config-version", "1.0"));
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
configVersion = 1.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
||||||
|
|
||||||
|
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
|
||||||
|
|
||||||
|
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
|
||||||
|
|
||||||
|
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
|
||||||
|
|
||||||
|
boolean debugMode = config.getOrElse("debug-mode", false);
|
||||||
|
|
||||||
|
String kickReconnectMessageString = config.getOrElse("disconnect-reset-message",
|
||||||
|
config.getOrElse("reconnect-message", "<red>Please reconnect.</red>"));
|
||||||
|
|
||||||
|
//Upgrade config
|
||||||
|
if (configVersion <= 2.0) {
|
||||||
|
Files.delete(path);
|
||||||
|
config = CommentedFileConfig.builder(path)
|
||||||
|
.defaultData(defaultConfigLocation)
|
||||||
|
.autosave()
|
||||||
|
.preserveInsertionOrder()
|
||||||
|
.sync()
|
||||||
|
.build();
|
||||||
|
config.load();
|
||||||
|
config.set("silence-warnings", silenceWarnings);
|
||||||
|
config.set("serverRedirectTimeout", serverSwitchCancellationTime);
|
||||||
|
config.set("bypass-registry-checks", bypassRegistryCheck);
|
||||||
|
config.set("bypass-mod-checks", bypassModCheck);
|
||||||
|
config.set("debug-mode", debugMode);
|
||||||
|
config.set("reconnect-message", kickReconnectMessageString);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean enableKickReset = config.getOrElse("enable-kick-reset", false);
|
||||||
|
|
||||||
|
return new AmbassadorConfig(silenceWarnings, bypassRegistryCheck, bypassModCheck,
|
||||||
|
debugMode, enableKickReset, kickReconnectMessageString);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean silenceWarnings = config.getOrElse("silence-warnings", false);
|
public int getServerSwitchCancellationTime() {
|
||||||
|
return serverSwitchCancellationTime;
|
||||||
//Upgrade config
|
|
||||||
if (configVersion <= 1.2) {
|
|
||||||
Files.delete(path);
|
|
||||||
config = CommentedFileConfig.builder(path)
|
|
||||||
.defaultData(defaultConfigLocation)
|
|
||||||
.autosave()
|
|
||||||
.preserveInsertionOrder()
|
|
||||||
.sync()
|
|
||||||
.build();
|
|
||||||
config.load();
|
|
||||||
config.set("silence-warnings", silenceWarnings);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int serverSwitchCancellationTime = config.getOrElse("serverRedirectTimeout", 30);
|
public boolean isSilenceWarnings() {
|
||||||
|
return silenceWarnings;
|
||||||
|
}
|
||||||
|
|
||||||
boolean bypassRegistryCheck = config.getOrElse("bypass-registry-checks", false);
|
public boolean isBypassRegistryCheck() {
|
||||||
|
return bypassRegistryCheck;
|
||||||
|
}
|
||||||
|
|
||||||
boolean bypassModCheck = config.getOrElse("bypass-mod-checks", false);
|
public boolean isBypassModCheck() {
|
||||||
|
return bypassModCheck;
|
||||||
|
}
|
||||||
|
|
||||||
boolean debugMode = config.getOrElse("debug-mode", false);
|
public boolean isDebugMode() {
|
||||||
|
return debugMode;
|
||||||
|
}
|
||||||
|
|
||||||
return new AmbassadorConfig(bypassRegistryCheck, bypassModCheck, silenceWarnings, debugMode);
|
public boolean isEnableKickReset() {
|
||||||
}
|
return enableKickReset;
|
||||||
|
}
|
||||||
|
|
||||||
public int getServerSwitchCancellationTime() {
|
public String getKickReconnectMessageString() {
|
||||||
return serverSwitchCancellationTime;
|
return kickReconnectMessageString;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isSilenceWarnings() {
|
|
||||||
return silenceWarnings;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBypassRegistryCheck() {
|
|
||||||
return bypassRegistryCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isBypassModCheck() {
|
|
||||||
return bypassModCheck;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isDebugMode() {
|
|
||||||
return debugMode;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package org.adde0109.ambassador.forge;
|
package org.adde0109.ambassador.forge;
|
||||||
|
|
||||||
|
import org.adde0109.ambassador.forge.packet.Context;
|
||||||
|
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.RegistryPacket;
|
import org.adde0109.ambassador.forge.packet.RegistryPacket;
|
||||||
|
|
||||||
|
|
@ -11,6 +13,7 @@ import java.util.zip.Checksum;
|
||||||
public class ForgeHandshake {
|
public class ForgeHandshake {
|
||||||
private ModListReplyPacket modListReplyPacket;
|
private ModListReplyPacket modListReplyPacket;
|
||||||
private final Map<String, Long> registries = new HashMap<>();
|
private final Map<String, Long> registries = new HashMap<>();
|
||||||
|
public GenericForgeLoginWrapperPacket<Context.ClientContext> zetaFlagsPacket;
|
||||||
|
|
||||||
public ForgeHandshake() {
|
public ForgeHandshake() {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,8 +6,8 @@ import com.google.common.io.ByteStreams;
|
||||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.handler.codec.DecoderException;
|
|
||||||
import org.adde0109.ambassador.forge.packet.Context;
|
import org.adde0109.ambassador.forge.packet.Context;
|
||||||
|
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
@ -117,30 +117,52 @@ public class ForgeHandshakeUtils {
|
||||||
return stream.toByteArray();
|
return stream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class SilentGearUtils {
|
public static class ThirdPartyRegistryUtils {
|
||||||
public static boolean isSilentGearPacket(byte[] data) {
|
|
||||||
ByteBuf buf = Unpooled.wrappedBuffer(data);
|
static enum ThirdPartyChannel {
|
||||||
String channel = null;
|
SILENTGEAR_NETWORK {
|
||||||
try {
|
@Override
|
||||||
channel = ProtocolUtils.readString(buf);
|
public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed) {
|
||||||
} catch (DecoderException e) {
|
return new ACKPacket(context, 3);
|
||||||
} finally {
|
}
|
||||||
buf.release();
|
},
|
||||||
}
|
ZETA_MAIN {
|
||||||
return channel != null && channel.equals("silentgear:network");
|
@Override
|
||||||
|
public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed) {
|
||||||
|
return new GenericForgeLoginWrapperPacket<Context.ClientContext>(completed.zetaFlagsPacket.getContent(), context);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
abstract public IForgeLoginWrapperPacket<Context.ClientContext> generateResponsePacket(Context.ClientContext context, ForgeHandshake completed);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
|
static boolean isThirdPartyPacket(GenericForgeLoginWrapperPacket<Context> packet) {
|
||||||
private final Context.ClientContext context;
|
try {
|
||||||
|
Enum.valueOf(ThirdPartyChannel.class,
|
||||||
public ACKPacket(Context.ClientContext context) {
|
packet.getContext().getChannelName().replace(':', '_').toUpperCase());
|
||||||
this.context = context;
|
return true;
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
@Override
|
}
|
||||||
|
|
||||||
|
static ThirdPartyChannel getThirdPartyChannel(GenericForgeLoginWrapperPacket<Context> packet) {
|
||||||
|
return Enum.valueOf(ThirdPartyChannel.class,
|
||||||
|
packet.getContext().getChannelName().replace(':', '_').toUpperCase());
|
||||||
|
}
|
||||||
|
|
||||||
|
static class ACKPacket implements IForgeLoginWrapperPacket<Context.ClientContext> {
|
||||||
|
|
||||||
|
private final Context.ClientContext context;
|
||||||
|
private final int packetID;
|
||||||
|
ACKPacket(Context.ClientContext context, int packetID) {
|
||||||
|
this.context = context;
|
||||||
|
this.packetID = packetID;
|
||||||
|
}
|
||||||
|
|
||||||
public ByteBuf encode() {
|
public ByteBuf encode() {
|
||||||
ByteBuf buf = Unpooled.buffer();
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
|
||||||
ProtocolUtils.writeVarInt(buf, 3);
|
ProtocolUtils.writeVarInt(buf, packetID);
|
||||||
|
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,8 @@ 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.StateRegistry;
|
|
||||||
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
import com.velocitypowered.proxy.protocol.packet.AvailableCommandsPacket;
|
||||||
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
import com.velocitypowered.proxy.protocol.packet.PluginMessagePacket;
|
||||||
import net.kyori.adventure.identity.Identity;
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.adde0109.ambassador.Ambassador;
|
import org.adde0109.ambassador.Ambassador;
|
||||||
import org.adde0109.ambassador.forge.packet.*;
|
import org.adde0109.ambassador.forge.packet.*;
|
||||||
|
|
@ -64,37 +62,22 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
VelocityForgeBackendConnectionPhase() {
|
VelocityForgeBackendConnectionPhase() {
|
||||||
}
|
}
|
||||||
|
|
||||||
public void handle(VelocityServerConnection server, ConnectedPlayer player, IForgeLoginWrapperPacket message) {
|
public void handle(VelocityServerConnection server, ConnectedPlayer player, IForgeLoginWrapperPacket<Context> message) {
|
||||||
VelocityForgeBackendConnectionPhase newPhase = getNewPhase(server,message);
|
VelocityForgeBackendConnectionPhase newPhase = getNewPhase(server,message);
|
||||||
|
|
||||||
server.setConnectionPhase(newPhase);
|
server.setConnectionPhase(newPhase);
|
||||||
|
|
||||||
if (player.getPhase() == VelocityForgeClientConnectionPhase.NOT_STARTED ||
|
|
||||||
player.getPhase() == VelocityForgeClientConnectionPhase.IN_PROGRESS) {
|
|
||||||
//Initial Forge
|
|
||||||
player.getConnection().write(message);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Forge -> Forge
|
//Forge -> Forge
|
||||||
|
|
||||||
//Reset client if not ready to receive new handshake
|
|
||||||
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.CRP ||
|
|
||||||
clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.SR) {
|
|
||||||
clientPhase.resetConnectionPhase(player);
|
if (!player.isActive()) {
|
||||||
player.getConnection().write(message);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!clientPhase.consideredComplete()) {
|
||||||
//STILL WIP
|
//Initial Forge
|
||||||
if (!Ambassador.getInstance().config.isDebugMode()) {
|
|
||||||
server.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.clientResetType.NONE) {
|
|
||||||
if (message instanceof ModListPacket modListPacket) {
|
if (message instanceof ModListPacket modListPacket) {
|
||||||
clientPhase.forgeHandshake = new ForgeHandshake();
|
clientPhase.forgeHandshake = new ForgeHandshake();
|
||||||
}
|
}
|
||||||
|
|
@ -103,6 +86,29 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
}
|
}
|
||||||
player.getConnection().write(message);
|
player.getConnection().write(message);
|
||||||
} else {
|
} else {
|
||||||
|
//Reset client if not ready to receive new handshake
|
||||||
|
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.CRP ||
|
||||||
|
clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.SR) {
|
||||||
|
clientPhase.resetConnectionPhase(player);
|
||||||
|
player.getConnection().write(message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (clientPhase.forgeHandshake.getModListReplyPacket() == null) {
|
||||||
|
//We have nothing to respond with during this handshake. Unable to proceed.
|
||||||
|
if (Ambassador.getInstance().config.isEnableKickReset()) {
|
||||||
|
//Kick-reset
|
||||||
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
|
} else {
|
||||||
|
Ambassador.getInstance().logger.error("Unable for {} to switch servers. Vanilla({}) -> Forge({}) switch " +
|
||||||
|
"without client side mod or kick-reset enabled is not yet supported!",
|
||||||
|
player.getGameProfile().getName(), player.getConnectedServer().getServerInfo().getName(),
|
||||||
|
server.getServerInfo().getName());
|
||||||
|
server.disconnect();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (message instanceof ModListPacket modListPacket) {
|
if (message instanceof ModListPacket modListPacket) {
|
||||||
remainingRegistries = new CountDownLatch(modListPacket.getRegistries().size());
|
remainingRegistries = new CountDownLatch(modListPacket.getRegistries().size());
|
||||||
|
|
||||||
|
|
@ -120,14 +126,17 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
}).thenAcceptAsync((v) -> {
|
}).thenAcceptAsync((v) -> {
|
||||||
|
|
||||||
if(Ambassador.getInstance().config.isDebugMode()) {
|
if(Ambassador.getInstance().config.isDebugMode()) {
|
||||||
player.sendMessage(Component.text("Handshake took: " + (System.currentTimeMillis()-time)/1000 + " seconds"));
|
player.sendMessage(Component.text("Handshake took: " + (System.currentTimeMillis()-time) + " ms"));
|
||||||
player.sendMessage(Component.text("Avg packet time" +
|
player.sendMessage(Component.text("Avg packet time" +
|
||||||
((System.currentTimeMillis()-time)/1000)/modListPacket.getRegistries().size() + " seconds"));
|
(System.currentTimeMillis()-time)/modListPacket.getRegistries().size() + " ms"));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
|
if (Ambassador.getInstance().config.isBypassRegistryCheck() ||
|
||||||
clientPhase.forgeHandshake.isCompatible(handshake)) {
|
clientPhase.forgeHandshake.isCompatible(handshake)) {
|
||||||
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
|
server.ensureConnected().write(clientPhase.forgeHandshake.getModListReplyPacket());
|
||||||
|
} else if (Ambassador.getInstance().config.isEnableKickReset()) {
|
||||||
|
//Kick-reset
|
||||||
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
} else {
|
} else {
|
||||||
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
|
Ambassador.getInstance().logger.error("Unable to switch due to the registries of " +
|
||||||
server.getServer().getServerInfo().getName() + " being different from the registries of " +
|
server.getServer().getServerInfo().getName() + " being different from the registries of " +
|
||||||
|
|
@ -141,10 +150,13 @@ public enum VelocityForgeBackendConnectionPhase implements BackendConnectionPhas
|
||||||
remainingRegistries.countDown();
|
remainingRegistries.countDown();
|
||||||
} else if (message instanceof ConfigDataPacket) {
|
} else if (message instanceof ConfigDataPacket) {
|
||||||
server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
|
server.getConnection().write(new ACKPacket(Context.fromContext(message.getContext(), true)));
|
||||||
} else if (message instanceof GenericForgeLoginWrapperPacket<?> packet
|
} else if (message instanceof GenericForgeLoginWrapperPacket<Context> packet
|
||||||
&& ForgeHandshakeUtils.SilentGearUtils.isSilentGearPacket(packet.getContent())) {
|
&& ForgeHandshakeUtils.ThirdPartyRegistryUtils.isThirdPartyPacket(packet)) {
|
||||||
server.getConnection().write(new ForgeHandshakeUtils.SilentGearUtils.ACKPacket(
|
server.getConnection().write(
|
||||||
Context.fromContext(message.getContext(), true)));
|
ForgeHandshakeUtils.ThirdPartyRegistryUtils.getThirdPartyChannel(packet).
|
||||||
|
generateResponsePacket(
|
||||||
|
Context.ClientContext.fromContext(packet.getContext(), true),
|
||||||
|
clientPhase.forgeHandshake));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//Forge server
|
//Forge server
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ import io.netty.buffer.Unpooled;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import org.adde0109.ambassador.Ambassador;
|
import org.adde0109.ambassador.Ambassador;
|
||||||
import org.adde0109.ambassador.forge.packet.Context;
|
import org.adde0109.ambassador.forge.packet.Context;
|
||||||
|
import org.adde0109.ambassador.forge.packet.GenericForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
import org.adde0109.ambassador.forge.packet.IForgeLoginWrapperPacket;
|
||||||
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
import org.adde0109.ambassador.forge.packet.ModListReplyPacket;
|
||||||
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
|
import org.adde0109.ambassador.velocity.client.FML2CRPMResetCompleteDecoder;
|
||||||
|
|
@ -24,6 +25,7 @@ import org.adde0109.ambassador.velocity.client.OutboundSuccessHolder;
|
||||||
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
import org.adde0109.ambassador.velocity.client.ClientPacketQueue;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase {
|
||||||
|
|
||||||
|
|
@ -37,17 +39,12 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
public void complete(ConnectedPlayer player) {
|
public void complete(ConnectedPlayer player) {
|
||||||
//When no handshake has taken place.
|
//When no handshake has taken place.
|
||||||
//Test if the client supports CRP.
|
//Test if the client supports CRP.
|
||||||
clientResetType.CRP.doReset(player);
|
ClientResetType.CRP.doReset(player);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean consideredComplete() {
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
IN_PROGRESS {
|
IN_PROGRESS {
|
||||||
},
|
},
|
||||||
WAITING_RESET {
|
WAITING_RESET() {
|
||||||
@Override
|
@Override
|
||||||
void onTransitionToNewPhase(ConnectedPlayer player) {
|
void onTransitionToNewPhase(ConnectedPlayer player) {
|
||||||
//We unregister so no plugin sees this client while the client is being reset.
|
//We unregister so no plugin sees this client while the client is being reset.
|
||||||
|
|
@ -70,7 +67,7 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
|
|
||||||
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
if (!(server.getConnection().getType() instanceof ForgeFMLConnectionType)) {
|
||||||
// -> vanilla
|
// -> vanilla
|
||||||
complete(player, ((Context.ClientContext) msg.getContext()).success() ? clientResetType.CRP : clientResetType.UNKNOWN);
|
complete(player, ((Context.ClientContext) msg.getContext()).success() ? ClientResetType.CRP : null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (player.getConnectionInFlight() != null) {
|
if (player.getConnectionInFlight() != null) {
|
||||||
|
|
@ -84,8 +81,15 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
COMPLETE {
|
COMPLETE {
|
||||||
|
|
||||||
|
private ClientResetType resetType = ClientResetType.UNKNOWN;
|
||||||
@Override
|
@Override
|
||||||
void onTransitionToNewPhase(ConnectedPlayer player) {
|
void onTransitionToNewPhase(ConnectedPlayer player) {
|
||||||
|
//Send Login Success to client
|
||||||
|
MinecraftConnection connection = player.getConnection();
|
||||||
|
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
|
||||||
|
.sendPacket();
|
||||||
|
connection.setState(StateRegistry.PLAY);
|
||||||
//Plugins may now send packets to client
|
//Plugins may now send packets to client
|
||||||
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
|
player.getConnection().getChannel().pipeline().remove(ForgeConstants.PLUGIN_PACKET_QUEUE);
|
||||||
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
|
((VelocityServer) Ambassador.getInstance().server).registerConnection(player);
|
||||||
|
|
@ -102,21 +106,35 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void complete(ConnectedPlayer player, clientResetType resetType) {
|
public void complete(ConnectedPlayer player) {
|
||||||
if (Ambassador.getInstance().config.isDebugMode()) {
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
player.sendMessage(Component.text("Forge -> Vanilla - Not resetting"));
|
player.sendMessage(Component.text("Not resetting"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setResetType(ConnectedPlayer player, ClientResetType resetType) {
|
||||||
|
this.resetType = resetType;
|
||||||
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
|
player.sendMessage(Component.text("Reset type: " + this.resetType.toString()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ClientResetType getResetType() {
|
||||||
|
return resetType;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
//TODO: Make a new class that's linked to each player with these fields instead of having them in this phase class
|
//TODO: Make a new class that's linked to each player with these fields instead of having them in this phase class
|
||||||
public ForgeHandshake forgeHandshake = new ForgeHandshake();
|
public ForgeHandshake forgeHandshake = new ForgeHandshake();
|
||||||
|
|
||||||
private clientResetType resetType = clientResetType.UNKNOWN;
|
|
||||||
|
|
||||||
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket<Context.ClientContext> msg, VelocityServerConnection server) {
|
public boolean handle(ConnectedPlayer player, IForgeLoginWrapperPacket<Context.ClientContext> msg, VelocityServerConnection server) {
|
||||||
|
|
||||||
|
if (msg.getContext().getChannelName().equals("zeta:main")) {
|
||||||
|
forgeHandshake.zetaFlagsPacket = (GenericForgeLoginWrapperPacket<Context.ClientContext>) msg;
|
||||||
|
}
|
||||||
|
|
||||||
if (msg instanceof ModListReplyPacket replyPacket) {
|
if (msg instanceof ModListReplyPacket replyPacket) {
|
||||||
ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map(
|
ModInfo modInfo = new ModInfo("FML2", replyPacket.getMods().stream().map(
|
||||||
(v) -> new ModInfo.Mod(v,"1")).toList());
|
(v) -> new ModInfo.Mod(v,"1")).toList());
|
||||||
|
|
@ -137,47 +155,24 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void complete(ConnectedPlayer player) {
|
public void complete(ConnectedPlayer player) {
|
||||||
complete(player, getResetType(player));
|
complete(player, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void complete(ConnectedPlayer player, clientResetType resetType) {
|
public void complete(ConnectedPlayer player, ClientResetType resetType) {
|
||||||
MinecraftConnection connection = player.getConnection();
|
|
||||||
//Send Login Success to client
|
|
||||||
((OutboundSuccessHolder) connection.getChannel().pipeline().get(ForgeConstants.SERVER_SUCCESS_LISTENER))
|
|
||||||
.sendPacket();
|
|
||||||
connection.setState(StateRegistry.PLAY);
|
|
||||||
|
|
||||||
//Change phase to COMPLETE
|
//Change phase to COMPLETE
|
||||||
player.setPhase(COMPLETE);
|
player.setPhase(COMPLETE);
|
||||||
COMPLETE.resetType = resetType;
|
|
||||||
COMPLETE.onTransitionToNewPhase(player);
|
COMPLETE.onTransitionToNewPhase(player);
|
||||||
COMPLETE.forgeHandshake = forgeHandshake;
|
COMPLETE.forgeHandshake = forgeHandshake;
|
||||||
|
if (resetType != null) {
|
||||||
|
COMPLETE.setResetType(player, resetType);
|
||||||
|
}
|
||||||
|
|
||||||
if (Ambassador.getInstance().config.isDebugMode()) {
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
player.sendMessage(Component.text("Forge handshake complete"));
|
player.sendMessage(Component.text("Forge handshake complete"));
|
||||||
player.sendMessage(Component.text("Reset type: " + resetType.toString()));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 onTransitionToNewPhase(ConnectedPlayer player) {
|
void onTransitionToNewPhase(ConnectedPlayer player) {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -191,10 +186,35 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public clientResetType getResetType() {
|
public ClientResetType getResetType() {
|
||||||
return resetType;
|
return COMPLETE.getResetType();
|
||||||
}
|
}
|
||||||
enum clientResetType {
|
|
||||||
|
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,
|
UNKNOWN,
|
||||||
NONE,
|
NONE,
|
||||||
CRP {
|
CRP {
|
||||||
|
|
@ -236,6 +256,8 @@ public enum VelocityForgeClientConnectionPhase implements ClientConnectionPhase
|
||||||
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
|
buf.writeBytes((player.getVirtualHost().get().getHostName() + ":"
|
||||||
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
|
+ player.getVirtualHost().get().getPort()).getBytes(StandardCharsets.UTF_8));
|
||||||
player.getConnection().write(new PluginMessagePacket("srvredirect:red", buf));
|
player.getConnection().write(new PluginMessagePacket("srvredirect:red", buf));
|
||||||
|
|
||||||
|
Ambassador.getInstance().reconnectSwitchPlayer(player);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -4,31 +4,38 @@ public class Context {
|
||||||
|
|
||||||
private final int responseID;
|
private final int responseID;
|
||||||
|
|
||||||
private Context(int responseID) {
|
private final String channelName;
|
||||||
|
|
||||||
|
private Context(int responseID, String channelName) {
|
||||||
this.responseID = responseID;
|
this.responseID = responseID;
|
||||||
|
this.channelName = channelName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Context createContext(int responseID) {
|
public static Context createContext(int responseID, String channelName) {
|
||||||
return new Context(responseID);
|
return new Context(responseID, channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientContext createClientContext(int responseID, boolean clientSuccess) {
|
public static ClientContext createClientContext(int responseID, boolean clientSuccess, String channelName) {
|
||||||
return new ClientContext(responseID,clientSuccess);
|
return new ClientContext(responseID, clientSuccess, channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ClientContext fromContext(Context context, boolean clientSuccess) {
|
public static ClientContext fromContext(Context context, boolean clientSuccess) {
|
||||||
return new ClientContext(context.responseID,clientSuccess);
|
return new ClientContext(context.responseID, clientSuccess, context.channelName);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getResponseID() {
|
public int getResponseID() {
|
||||||
return responseID;
|
return responseID;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getChannelName() {
|
||||||
|
return channelName;
|
||||||
|
}
|
||||||
|
|
||||||
public static class ClientContext extends Context {
|
public static class ClientContext extends Context {
|
||||||
|
|
||||||
private final boolean clientSuccess;
|
private final boolean clientSuccess;
|
||||||
ClientContext(int responseID, boolean clientSuccess) {
|
ClientContext(int responseID, boolean clientSuccess, String channelName) {
|
||||||
super(responseID);
|
super(responseID, channelName);
|
||||||
this.clientSuccess = clientSuccess;
|
this.clientSuccess = clientSuccess;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@ public class GenericForgeLoginWrapperPacket<T extends Context> implements IForge
|
||||||
private final byte[] content;
|
private final byte[] content;
|
||||||
private final T context;
|
private final T context;
|
||||||
|
|
||||||
GenericForgeLoginWrapperPacket(byte[] content, T context) {
|
public GenericForgeLoginWrapperPacket(byte[] content, T context) {
|
||||||
this.content = content;
|
this.content = content;
|
||||||
this.context = context;
|
this.context = context;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,39 @@
|
||||||
package org.adde0109.ambassador.forge.packet;
|
package org.adde0109.ambassador.forge.packet;
|
||||||
|
|
||||||
|
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||||
import io.netty.buffer.ByteBuf;
|
import io.netty.buffer.ByteBuf;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
|
||||||
public class ModDataPacket extends GenericForgeLoginWrapperPacket<Context> {
|
public class ModDataPacket implements IForgeLoginWrapperPacket<Context> {
|
||||||
|
private final byte[] content;
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
ModDataPacket(byte[] content, Context context) {
|
ModDataPacket(byte[] content, Context context) {
|
||||||
super(content, context);
|
this.content = content;
|
||||||
|
this.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
static public ModDataPacket read(ByteBuf input, Context context) {
|
||||||
|
byte[] content = new byte[input.readableBytes()];
|
||||||
|
input.readBytes(content);
|
||||||
|
return new ModDataPacket(content, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ByteBuf encode() {
|
||||||
|
ByteBuf buf = Unpooled.buffer();
|
||||||
|
ProtocolUtils.writeVarInt(buf, 5); //PacketID
|
||||||
|
buf.writeBytes(content);
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
public byte[] getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Context getContext() {
|
||||||
|
return context;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ public class ModListPacket implements IForgeLoginWrapperPacket<Context> {
|
||||||
registries.add(ProtocolUtils.readString(input, 32767));
|
registries.add(ProtocolUtils.readString(input, 32767));
|
||||||
|
|
||||||
List<String> dataPackRegistries = null;
|
List<String> dataPackRegistries = null;
|
||||||
if (FML3) {
|
if (FML3 && input.isReadable()) {
|
||||||
dataPackRegistries = new ArrayList<>();
|
dataPackRegistries = new ArrayList<>();
|
||||||
len = ProtocolUtils.readVarInt(input);
|
len = ProtocolUtils.readVarInt(input);
|
||||||
for (int x = 0; x < len; x++)
|
for (int x = 0; x < len; x++)
|
||||||
|
|
|
||||||
|
|
@ -9,106 +9,137 @@ import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.ChannelHandlerContext;
|
import io.netty.channel.ChannelHandlerContext;
|
||||||
import io.netty.handler.codec.DecoderException;
|
import io.netty.handler.codec.DecoderException;
|
||||||
import io.netty.handler.codec.MessageToMessageCodec;
|
import io.netty.handler.codec.MessageToMessageCodec;
|
||||||
import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
|
import io.netty.util.ReferenceCountUtil;
|
||||||
|
import org.adde0109.ambassador.Ambassador;
|
||||||
import org.adde0109.ambassador.forge.packet.*;
|
import org.adde0109.ambassador.forge.packet.*;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> {
|
public class ForgeLoginWrapperCodec extends MessageToMessageCodec<DeferredByteBufHolder, IForgeLoginWrapperPacket<?>> {
|
||||||
|
|
||||||
private final boolean FML3;
|
private final boolean FML3;
|
||||||
private final List<Integer> loginWrapperIDs = new ArrayList<>();
|
private final Map<Integer, Context> loginWrapperContexts = new HashMap<>();
|
||||||
|
|
||||||
public ForgeLoginWrapperCodec(boolean fml3) {
|
public ForgeLoginWrapperCodec(boolean fml3) {
|
||||||
FML3 = fml3;
|
FML3 = fml3;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean acceptInboundMessage(Object msg) throws Exception {
|
||||||
|
return (msg instanceof LoginPluginMessagePacket
|
||||||
|
&& ((LoginPluginMessagePacket) msg).getChannel().equals("fml:loginwrapper"))
|
||||||
|
|| (msg instanceof LoginPluginResponsePacket
|
||||||
|
&& loginWrapperContexts.containsKey(((LoginPluginResponsePacket) msg).getId()));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, DeferredByteBufHolder in, List<Object> out) throws Exception {
|
||||||
|
|
||||||
ByteBuf buf = in.content();
|
ByteBuf buf = in.content();
|
||||||
|
|
||||||
Context context;
|
Context context;
|
||||||
if (in instanceof LoginPluginMessagePacket msg && msg.getChannel().equals("fml:loginwrapper")) {
|
if (in instanceof LoginPluginResponsePacket msg) {
|
||||||
context = Context.createContext(msg.getId());
|
//Continue from stored context
|
||||||
} else if (in instanceof LoginPluginResponsePacket msg && loginWrapperIDs.remove(Integer.valueOf(msg.getId()))) {
|
context = Context.fromContext(
|
||||||
context = Context.createClientContext(msg.getId(), msg.isSuccess());
|
loginWrapperContexts.remove(((LoginPluginResponsePacket) msg).getId()), msg.isSuccess());
|
||||||
|
if (!msg.isSuccess()) {
|
||||||
|
//Nothing to read, just create an empty packet.
|
||||||
|
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
String channel = ProtocolUtils.readString(buf); //Read the channel even though we know the channel by context.
|
||||||
|
Ambassador.getInstance();
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
ctx.fireChannelRead(in.retain());
|
//New context.
|
||||||
return;
|
LoginPluginMessagePacket msg = (LoginPluginMessagePacket) in;
|
||||||
|
String channel = ProtocolUtils.readString(buf);
|
||||||
|
|
||||||
|
context = Context.createContext(msg.getId(), channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Decoding of data starts here - channel already read
|
||||||
int originalReaderIndex = buf.readerIndex();
|
int originalReaderIndex = buf.readerIndex();
|
||||||
try {
|
|
||||||
String channel = ProtocolUtils.readString(buf);
|
if (!context.getChannelName().equals("fml:handshake")) {
|
||||||
if (!channel.equals("fml:handshake")) {
|
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
||||||
throw new DecoderException();
|
} else {
|
||||||
|
int length = ProtocolUtils.readVarInt(buf);
|
||||||
|
int packetID = ProtocolUtils.readVarInt(buf);
|
||||||
|
if (context instanceof Context.ClientContext clientContext) {
|
||||||
|
switch (packetID) {
|
||||||
|
case 2:
|
||||||
|
out.add(ModListReplyPacket.read(buf, clientContext));
|
||||||
|
break;
|
||||||
|
case 99:
|
||||||
|
out.add(new ACKPacket(clientContext));
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
//Undo decoding
|
||||||
|
buf.readerIndex(originalReaderIndex);
|
||||||
|
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
||||||
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
|
Ambassador.getInstance().logger.warn(
|
||||||
|
"Unrecognised packet id received from client on fml:handshake: " + packetID);
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
int length = ProtocolUtils.readVarInt(buf);
|
switch (packetID) {
|
||||||
int packetID = ProtocolUtils.readVarInt(buf);
|
case 1:
|
||||||
if (context instanceof Context.ClientContext clientContext) {
|
out.add(ModListPacket.read(buf, context, FML3));
|
||||||
switch (packetID) {
|
break;
|
||||||
case 2:
|
case 3:
|
||||||
out.add(ModListReplyPacket.read(buf, clientContext));
|
out.add(RegistryPacket.read(buf, context, FML3));
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
out.add(ConfigDataPacket.read(buf, context, FML3));
|
||||||
|
break;
|
||||||
|
case 5:
|
||||||
|
if (FML3) {
|
||||||
|
out.add(ModDataPacket.read(buf, context));
|
||||||
break;
|
break;
|
||||||
case 99:
|
}
|
||||||
out.add(new ACKPacket(clientContext));
|
default:
|
||||||
break;
|
//Undo decoding
|
||||||
default:
|
buf.readerIndex(originalReaderIndex);
|
||||||
throw new DecoderException();
|
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
||||||
}
|
if (Ambassador.getInstance().config.isDebugMode()) {
|
||||||
} else {
|
Ambassador.getInstance().logger.warn(
|
||||||
switch (packetID) {
|
"Unrecognised packet id received from server on fml:handshake: " + packetID);
|
||||||
case 1:
|
}
|
||||||
out.add(ModListPacket.read(buf, context, FML3));
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
out.add(RegistryPacket.read(buf, context, FML3));
|
|
||||||
break;
|
|
||||||
case 4:
|
|
||||||
out.add(ConfigDataPacket.read(buf, context, FML3));
|
|
||||||
break;
|
|
||||||
case 5:
|
|
||||||
if (FML3) {
|
|
||||||
buf.readerIndex(originalReaderIndex);
|
|
||||||
out.add(ModDataPacket.read(buf, context));
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new DecoderException();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (DecoderException e) {
|
|
||||||
buf.readerIndex(originalReaderIndex);
|
|
||||||
out.add(GenericForgeLoginWrapperPacket.read(buf, context));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
|
protected void encode(ChannelHandlerContext ctx, IForgeLoginWrapperPacket<?> msg, List<Object> out) throws Exception {
|
||||||
ByteBuf wrapped;
|
ByteBuf wrapped;
|
||||||
if (msg instanceof GenericForgeLoginWrapperPacket<?>) {
|
|
||||||
wrapped = msg.encode();
|
boolean data = !(msg.getContext() instanceof Context.ClientContext clientContext && !clientContext.success());
|
||||||
} else {
|
|
||||||
String channel = "fml:handshake";
|
boolean includeLength = !(msg instanceof GenericForgeLoginWrapperPacket);
|
||||||
if (msg instanceof ForgeHandshakeUtils.SilentGearUtils.ACKPacket) {
|
|
||||||
channel = "silentgear:network";
|
String channel = msg.getContext().getChannelName();
|
||||||
}
|
|
||||||
wrapped = Unpooled.buffer();
|
wrapped = Unpooled.buffer();
|
||||||
|
|
||||||
|
if (data) {
|
||||||
ByteBuf encoded = msg.encode();
|
ByteBuf encoded = msg.encode();
|
||||||
ProtocolUtils.writeString(wrapped, channel);
|
ProtocolUtils.writeString(wrapped, channel);
|
||||||
ProtocolUtils.writeVarInt(wrapped, encoded.readableBytes());
|
if (includeLength)
|
||||||
|
ProtocolUtils.writeVarInt(wrapped, encoded.readableBytes());
|
||||||
wrapped.writeBytes(encoded);
|
wrapped.writeBytes(encoded);
|
||||||
encoded.release();
|
encoded.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (msg.getContext() instanceof Context.ClientContext clientContext) {
|
if (msg.getContext() instanceof Context.ClientContext clientContext) {
|
||||||
out.add(new LoginPluginResponsePacket(clientContext.getResponseID(), clientContext.success(), wrapped));
|
out.add(new LoginPluginResponsePacket(clientContext.getResponseID(), clientContext.success(), wrapped));
|
||||||
} else {
|
} else {
|
||||||
out.add(new LoginPluginMessagePacket(msg.getContext().getResponseID(), "fml:loginwrapper", wrapped));
|
out.add(new LoginPluginMessagePacket(msg.getContext().getResponseID(), "fml:loginwrapper", wrapped));
|
||||||
if (!(msg instanceof ModDataPacket)) {
|
if (!(msg instanceof ModDataPacket)) { //ModDataPacket doesn't require a response
|
||||||
this.loginWrapperIDs.add(msg.getContext().getResponseID());
|
this.loginWrapperContexts.put(msg.getContext().getResponseID(), msg.getContext());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -71,6 +71,12 @@ public class VelocityEventHandler {
|
||||||
player.setModInfo(new ModInfo("Channels", event.getChannels().stream().map((id) -> {
|
player.setModInfo(new ModInfo("Channels", event.getChannels().stream().map((id) -> {
|
||||||
return new ModInfo.Mod(id.getId(), "");
|
return new ModInfo.Mod(id.getId(), "");
|
||||||
}).toList()));
|
}).toList()));
|
||||||
|
|
||||||
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
|
//If reset typ is still unknown, set it!
|
||||||
|
if (clientPhase.getResetType() == VelocityForgeClientConnectionPhase.ClientResetType.UNKNOWN) {
|
||||||
|
clientPhase.updateResetType(player);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -37,7 +37,8 @@ public class ForgeLoginSessionHandler implements MinecraftSessionHandler {
|
||||||
if (serverConnection.getConnection() != null) {
|
if (serverConnection.getConnection() != null) {
|
||||||
ConnectedPlayer player = serverConnection.getPlayer();
|
ConnectedPlayer player = serverConnection.getPlayer();
|
||||||
|
|
||||||
((VelocityForgeClientConnectionPhase) player.getPhase()).complete(player);
|
VelocityForgeClientConnectionPhase clientPhase = (VelocityForgeClientConnectionPhase) player.getPhase();
|
||||||
|
clientPhase.complete(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ public class FML2CRPMResetCompleteDecoder extends ChannelInboundHandlerAdapter {
|
||||||
if (id == 98) {
|
if (id == 98) {
|
||||||
try {
|
try {
|
||||||
ctx.fireChannelRead(GenericForgeLoginWrapperPacket.read(
|
ctx.fireChannelRead(GenericForgeLoginWrapperPacket.read(
|
||||||
Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success)));
|
Unpooled.EMPTY_BUFFER, Context.createClientContext(id, success, "fml:handshake")));
|
||||||
} finally {
|
} finally {
|
||||||
buf.release();
|
buf.release();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
# Do not change this
|
# Do not change this
|
||||||
config-version = "2.0"
|
config-version = "2.1"
|
||||||
|
|
||||||
# How much time the player has to reconnect before canceling the server switch. (In seconds)
|
# How much time the player has to reconnect before canceling the server switch. (In seconds)
|
||||||
# Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
|
# Only for players with ServerRedirect mod installed. Set to -1 to disable ServerRedirect mod support.
|
||||||
|
|
@ -17,5 +17,13 @@ bypass-registry-checks = false
|
||||||
# Warning: This is a safety measure for when bypassRegistryCheck is true. Setting this to also true can cause crashes.
|
# Warning: This is a safety measure for when bypassRegistryCheck is true. Setting this to also true can cause crashes.
|
||||||
bypass-mod-checks = false
|
bypass-mod-checks = false
|
||||||
|
|
||||||
#Only for debug/troubleshooting
|
# Only for debug/troubleshooting
|
||||||
debug-mode = false
|
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