Works
This commit is contained in:
parent
007ff8511a
commit
2a4aba72e9
|
|
@ -1,11 +1,8 @@
|
|||
package org.adde0109.ambassador;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.player.PlayerChooseInitialServerEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyReloadEvent;
|
||||
import com.velocitypowered.api.plugin.Plugin;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
|
|
@ -16,10 +13,6 @@ import java.util.concurrent.Callable;
|
|||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import org.adde0109.ambassador.forge.ForgeConnection;
|
||||
import org.adde0109.ambassador.forge.ForgeHandshakeHandler;
|
||||
import org.adde0109.ambassador.forge.ForgeHandshakeUtils;
|
||||
import org.adde0109.ambassador.forge.ForgeServerSwitchHandler;
|
||||
import org.adde0109.ambassador.velocity.VelocityBackendChannelInitializer;
|
||||
import org.adde0109.ambassador.velocity.VelocityServerChannelInitializer;
|
||||
import org.adde0109.ambassador.velocity.VelocityEventHandler;
|
||||
|
|
@ -34,12 +27,9 @@ public class Ambassador {
|
|||
|
||||
public ProxyServer server;
|
||||
public final Logger logger;
|
||||
public AmbassadorConfig config;
|
||||
private final Metrics.Factory metricsFactory;
|
||||
private final Path dataDirectory;
|
||||
|
||||
public ForgeHandshakeHandler forgeHandshakeHandler;
|
||||
public ForgeServerSwitchHandler forgeServerSwitchHandler;
|
||||
|
||||
|
||||
|
||||
|
|
@ -55,18 +45,8 @@ public class Ambassador {
|
|||
public void onProxyInitialization(ProxyInitializeEvent event) throws ReflectiveOperationException {
|
||||
initMetrics();
|
||||
|
||||
config = AmbassadorConfig.readOrCreateConfig(dataDirectory,server,logger);
|
||||
if(config != null) {
|
||||
forgeHandshakeHandler = new ForgeHandshakeHandler(this);
|
||||
forgeServerSwitchHandler = new ForgeServerSwitchHandler(this);
|
||||
server.getEventManager().register(this, new VelocityEventHandler(this));
|
||||
server.getEventManager().register(this,forgeServerSwitchHandler);
|
||||
}
|
||||
else {
|
||||
logger.warn("Ambassador will be disabled because of errors");
|
||||
}
|
||||
server.getEventManager().register(this, new VelocityEventHandler(this));
|
||||
|
||||
ForgeHandshakeUtils.HandshakeReceiver.logger = logger;
|
||||
inject();
|
||||
}
|
||||
|
||||
|
|
@ -81,39 +61,7 @@ public class Ambassador {
|
|||
((ConnectionManager) cmField.get(server)).backendChannelInitializer.set(new VelocityBackendChannelInitializer(originalBackend,(VelocityServer) server));
|
||||
}
|
||||
|
||||
@Subscribe
|
||||
public void onProxyReloadEvent(ProxyReloadEvent event) {
|
||||
AmbassadorConfig c = AmbassadorConfig.readOrCreateConfig(dataDirectory,server,logger);
|
||||
if (config != null) {
|
||||
config = c;
|
||||
logger.info("Successfully reloaded the config");
|
||||
} else {
|
||||
logger.warn("Using the old config");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Subscribe
|
||||
public void onPlayerChooseInitialServerEvent(PlayerChooseInitialServerEvent event, Continuation continuation) {
|
||||
//Only handle Forge connections
|
||||
if((event.getInitialServer().isPresent()) && (forgeHandshakeHandler.getForgeConnection(event.getPlayer()).isPresent())) {
|
||||
//Forge client
|
||||
ForgeConnection forgeConnection = forgeHandshakeHandler.getForgeConnection(event.getPlayer()).get();
|
||||
if (forgeConnection.isForced()) {
|
||||
event.setInitialServer(forgeConnection.getSyncedServer().get());
|
||||
}
|
||||
forgeConnection.setForced(config.getForced(forgeConnection.getConnection().getProtocolVersion().getProtocol()));
|
||||
}
|
||||
continuation.resume();
|
||||
}
|
||||
|
||||
private void initMetrics() {
|
||||
Metrics metrics = metricsFactory.make(this, 15655);
|
||||
metrics.addCustomChart(new SingleLineChart("modern_forge_players", new Callable<Integer>() {
|
||||
@Override
|
||||
public Integer call() throws Exception {
|
||||
return (forgeHandshakeHandler != null) ? forgeHandshakeHandler.getAmountOfForgeConnections() : 0;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,164 +0,0 @@
|
|||
package org.adde0109.ambassador;
|
||||
|
||||
import com.electronwill.nightconfig.core.CommentedConfig;
|
||||
import com.electronwill.nightconfig.core.UnmodifiableConfig;
|
||||
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class AmbassadorConfig {
|
||||
|
||||
private static final int CONFIG_VERSION = 1;
|
||||
private final ProxyServer server;
|
||||
private final Logger logger;
|
||||
private Differentiators differentiatorsSettings;
|
||||
private ReSync reSyncSettings;
|
||||
|
||||
|
||||
private AmbassadorConfig(ProxyServer server,Logger logger) {
|
||||
this.server = server;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public RegisteredServer getServer(int protocolVersion) {
|
||||
return differentiatorsSettings.differentiators.get(protocolVersion).handshakeServer;
|
||||
}
|
||||
|
||||
public boolean getForced (int protocolVersion) {
|
||||
return differentiatorsSettings.differentiators.get(protocolVersion).forced;
|
||||
}
|
||||
|
||||
public boolean shouldHandle(int protocolVersion) {
|
||||
return differentiatorsSettings.differentiators.containsKey(protocolVersion);
|
||||
}
|
||||
|
||||
public int getReSyncTimeout() {
|
||||
return reSyncSettings.reSyncTimeout;
|
||||
}
|
||||
|
||||
public reSyncOption reSyncOptionForge() {
|
||||
return reSyncSettings.reSyncForgeForge;
|
||||
}
|
||||
|
||||
public reSyncOption reSyncOptionVanilla() {
|
||||
return reSyncSettings.reSyncForgeVanilla;
|
||||
}
|
||||
|
||||
|
||||
public static AmbassadorConfig readOrCreateConfig(Path dataDirectory,ProxyServer server, Logger logger) {
|
||||
AmbassadorConfig ambassadorConfig = new AmbassadorConfig(server,logger);
|
||||
try {
|
||||
Files.createDirectories(dataDirectory);
|
||||
|
||||
}
|
||||
catch (FileAlreadyExistsException ignored) {
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Config related error: " + e);
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
CommentedFileConfig config = CommentedFileConfig.builder(dataDirectory.resolve("ambassador.toml"))
|
||||
.defaultData(Ambassador.class.getClassLoader().getResource("default-ambassador.toml"))
|
||||
.autosave()
|
||||
.preserveInsertionOrder()
|
||||
.sync()
|
||||
.build();
|
||||
config.load();
|
||||
|
||||
if (config.getOrElse("config-version",0) != CONFIG_VERSION) {
|
||||
throw new Exception("Incompatible config-version detected! Please delete 'ambassador.toml' and reload.");
|
||||
}
|
||||
CommentedConfig differentiatorsSettingsConfig = config.get("Differentiators");
|
||||
CommentedConfig reSyncSettingsConfig = config.get("ReSync");
|
||||
|
||||
|
||||
ambassadorConfig.differentiatorsSettings = ambassadorConfig.new Differentiators(differentiatorsSettingsConfig);
|
||||
ambassadorConfig.reSyncSettings = ambassadorConfig.new ReSync(reSyncSettingsConfig);
|
||||
|
||||
|
||||
config.save();
|
||||
return ambassadorConfig;
|
||||
}
|
||||
catch (Exception e) {
|
||||
logger.error("Config related error: " + e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class ReSync {
|
||||
private int reSyncTimeout = 30;
|
||||
private reSyncOption reSyncForgeForge = reSyncOption.ALWAYS;
|
||||
private reSyncOption reSyncForgeVanilla = reSyncOption.NEVER;
|
||||
|
||||
private ReSync(CommentedConfig config) {
|
||||
if (config != null) {
|
||||
reSyncTimeout = config.getOrElse("resync-timeout",reSyncTimeout);
|
||||
reSyncForgeForge = reSyncOption.valueOf(
|
||||
config.getOrElse("resync-forge-to-forge",reSyncForgeForge.name()).toUpperCase());
|
||||
}
|
||||
reSyncForgeVanilla = reSyncOption.valueOf(
|
||||
config.getOrElse("unsync-forge-to-vanilla",reSyncForgeVanilla.name()).toUpperCase());
|
||||
}
|
||||
}
|
||||
|
||||
public enum reSyncOption {
|
||||
NEVER,
|
||||
ASK,
|
||||
ALWAYS
|
||||
}
|
||||
|
||||
private class Differentiators {
|
||||
private Map<Integer,DifferentiatorSettings> differentiators = ImmutableMap.of(
|
||||
758, new DifferentiatorSettings(),
|
||||
754, new DifferentiatorSettings()
|
||||
);
|
||||
private Differentiators(){
|
||||
}
|
||||
|
||||
private Differentiators(CommentedConfig config) throws Exception {
|
||||
if (config != null) {
|
||||
Map<Integer,DifferentiatorSettings> differentiators = new HashMap<>();
|
||||
for (UnmodifiableConfig.Entry entry : config.entrySet()) {
|
||||
if (entry.getValue() instanceof CommentedConfig) {
|
||||
differentiators.put(Integer.decode(entry.getKey()),new DifferentiatorSettings(entry.getValue()));
|
||||
}
|
||||
}
|
||||
this.differentiators = ImmutableMap.copyOf(differentiators);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class DifferentiatorSettings {
|
||||
private RegisteredServer handshakeServer = null;
|
||||
private boolean forced = false;
|
||||
|
||||
private DifferentiatorSettings(){
|
||||
}
|
||||
|
||||
private DifferentiatorSettings(CommentedConfig config) throws Exception {
|
||||
if (config != null) {
|
||||
String serverName = config.getOrElse("default-forge-server", "");
|
||||
if (!Objects.equals(serverName, ""))
|
||||
handshakeServer = server.getServer(serverName)
|
||||
.orElseThrow(() -> new Exception(serverName + "is not a registered server!"));
|
||||
this.forced = config.getOrElse("forced",forced);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,168 +0,0 @@
|
|||
package org.adde0109.ambassador.forge;
|
||||
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.LoginPhaseConnection;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ForgeConnection {
|
||||
|
||||
private final Logger logger;
|
||||
private final LoginPhaseConnection connection;
|
||||
|
||||
private Optional<byte[]> recivedClientModlist = Optional.empty();
|
||||
private static byte[] recivedClientACK;
|
||||
private boolean ignoreSyncExepction = false;
|
||||
|
||||
private Optional<ForgeHandshakeUtils.CachedServerHandshake> transmittedHandshake = Optional.empty();
|
||||
private boolean forced = false;
|
||||
private Optional<RegisteredServer> syncedTo = Optional.empty();
|
||||
|
||||
private boolean resettable;
|
||||
|
||||
public ForgeConnection(LoginPhaseConnection connection, Logger logger) {
|
||||
this.connection = connection;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
public CompletableFuture<Boolean> testIfForge(LoginPhaseConnection connection) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
|
||||
byte[] testPacket = ForgeHandshakeUtils.generateTestPacket();
|
||||
//This gets also sent to vanilla
|
||||
connection.sendLoginPluginMessage(MinecraftChannelIdentifier.create("fml", "loginwrapper"), testPacket,
|
||||
responseBody -> {
|
||||
future.complete(responseBody != null);
|
||||
ignoreSyncExepction = responseBody == null;
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public CompletableFuture<Boolean> sync(ForgeServerConnection forgeServerConnection) {
|
||||
CompletableFuture<Boolean> future = new CompletableFuture<>();
|
||||
forgeServerConnection.getHandshake().whenComplete((msg,ex) -> {
|
||||
if (ex != null) {
|
||||
future.complete(false);
|
||||
logger.warn("Sync Exception: " + ex);
|
||||
} else {
|
||||
//This gets also sent to vanilla
|
||||
sendModlist(msg.modListPacket).thenAccept((response) -> {
|
||||
if (!ignoreSyncExepction && response == null) {
|
||||
logger.warn("Sync Exception: Client responded with an empty body.");
|
||||
}
|
||||
if (response != null) {
|
||||
recivedClientModlist = Optional.of(response);
|
||||
//If the client is resettable.
|
||||
ByteBuf clientModListPacket = Unpooled.wrappedBuffer(response);
|
||||
clientModListPacket.readBytes(14); //Channel Identifier
|
||||
ProtocolUtils.readVarInt(clientModListPacket); //Length
|
||||
int packetID = ProtocolUtils.readVarInt(clientModListPacket);
|
||||
String[] mods = ProtocolUtils.readStringArray(clientModListPacket);
|
||||
resettable = Arrays.stream(mods).anyMatch((s) -> s.equals("clientresetpacket"));
|
||||
clientModListPacket.release();
|
||||
}
|
||||
});
|
||||
//This gets also sent to vanilla
|
||||
sendOther(msg.otherPackets).thenAccept((response) -> {
|
||||
if (!ignoreSyncExepction && response == null) {
|
||||
logger.warn("Sync Exception: Client responded with an empty body.");
|
||||
}
|
||||
//TODO: Generate the ACK packet ourself.
|
||||
ForgeConnection.recivedClientACK = (response == null) ? ForgeConnection.recivedClientACK : response;
|
||||
transmittedHandshake = Optional.of(msg);
|
||||
syncedTo = Optional.of(forgeServerConnection.getServer());
|
||||
});
|
||||
future.complete(true);
|
||||
}
|
||||
});
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompletableFuture<byte[]> sendModlist(byte[] modListPacket) {
|
||||
CompletableFuture<byte[]> future = new CompletableFuture<>();
|
||||
connection.sendLoginPluginMessage(MinecraftChannelIdentifier.create("fml", "loginwrapper"), modListPacket,
|
||||
future::complete);
|
||||
return future;
|
||||
}
|
||||
|
||||
private CompletableFuture<byte[]> sendOther(List<byte[]> otherPackets) {
|
||||
CompletableFuture<byte[]> future = new CompletableFuture<>();
|
||||
for (int i = 0; i < otherPackets.size(); i++) {
|
||||
connection.sendLoginPluginMessage(MinecraftChannelIdentifier.create("fml", "loginwrapper"), otherPackets.get(i),
|
||||
(i < (otherPackets.size() - 1)) ? responseBody -> {
|
||||
} : future::complete);
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
public void handleServerHandshakePacket(ServerLoginPluginMessageEvent event, Continuation continuation) {
|
||||
ByteArrayDataInput data = event.contentsAsDataStream();
|
||||
if (data.skipBytes(14) != 14) { //Channel Identifier
|
||||
continuation.resumeWithException(new EOFException());
|
||||
return;
|
||||
}
|
||||
ForgeHandshakeUtils.readVarInt(data); //Length
|
||||
int packetID = ForgeHandshakeUtils.readVarInt(data);
|
||||
|
||||
if (packetID == 1) {
|
||||
if (getRecivedClientModlist().isPresent()) {
|
||||
event.setResult(ServerLoginPluginMessageEvent.ResponseResult.reply(getRecivedClientModlist().get()));
|
||||
} else {
|
||||
continuation.resumeWithException(new Exception("Client isn't synced. This should have been caught" +
|
||||
" during serverPreConnect"));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (getRecivedClientACK() != null) {
|
||||
event.setResult(ServerLoginPluginMessageEvent.ResponseResult.reply(getRecivedClientACK()));
|
||||
} else {
|
||||
continuation.resumeWithException(new Exception("No response available."));
|
||||
return;
|
||||
}
|
||||
}
|
||||
continuation.resume();
|
||||
}
|
||||
|
||||
public LoginPhaseConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public boolean isResettable() {
|
||||
return resettable;
|
||||
}
|
||||
|
||||
public Optional<ForgeHandshakeUtils.CachedServerHandshake> getTransmittedHandshake() {
|
||||
return transmittedHandshake;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getRecivedClientModlist() {
|
||||
return recivedClientModlist;
|
||||
}
|
||||
|
||||
public static byte[] getRecivedClientACK() {
|
||||
return recivedClientACK;
|
||||
}
|
||||
public Optional<RegisteredServer> getSyncedServer() {
|
||||
return syncedTo;
|
||||
}
|
||||
public void setForced(boolean forced) {
|
||||
this.forced = forced;
|
||||
}
|
||||
public boolean isForced() {
|
||||
return forced;
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,7 @@ import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
|||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase;
|
||||
import org.adde0109.ambassador.velocity.VelocityForgeHandshakeSessionHandler;
|
||||
import org.checkerframework.checker.units.qual.A;
|
||||
|
|
@ -45,13 +46,10 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
|
|||
public void handleLogin(ConnectedPlayer player,ForgeHandshakeUtils.CachedServerHandshake handshake, Continuation continuation) {
|
||||
final MinecraftConnection connection = player.getConnection();
|
||||
VelocityForgeHandshakeSessionHandler sessionHandler = new VelocityForgeHandshakeSessionHandler(connection.getSessionHandler(), player);
|
||||
if (handshake == null) {
|
||||
connection.delayedWrite(new LoginPluginMessage(98, "fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
|
||||
listenerList.add(98);
|
||||
this.whenComplete = () -> {
|
||||
this.clientPhase = ClientPhase.VANILLA;
|
||||
continuation.resume();
|
||||
};
|
||||
//Without initial modlist for now
|
||||
if (true) {
|
||||
connection.delayedWrite(new LoginPluginMessage(0, "fml:loginwrapper", Unpooled.wrappedBuffer(ForgeHandshakeUtils.emptyModlist)));
|
||||
listenerList.add(0);
|
||||
} else {
|
||||
connection.delayedWrite(new LoginPluginMessage(0, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket)));
|
||||
listenerList.add(0);
|
||||
|
|
@ -59,11 +57,11 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
|
|||
connection.delayedWrite(new LoginPluginMessage(i + 1, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.otherPackets.get(i))));
|
||||
listenerList.add(i + 1);
|
||||
}
|
||||
this.whenComplete = () -> {
|
||||
this.clientPhase = ClientPhase.MODDED;
|
||||
continuation.resume();
|
||||
};
|
||||
}
|
||||
this.whenComplete = () -> {
|
||||
this.clientPhase = ClientPhase.MODDED;
|
||||
continuation.resume();
|
||||
};
|
||||
connection.setSessionHandler(sessionHandler);
|
||||
connection.flush();
|
||||
}
|
||||
|
|
@ -72,9 +70,11 @@ public class ForgeFML2ClientConnectionPhase implements VelocityForgeClientConnec
|
|||
public boolean handle(ConnectedPlayer player, LoginPluginResponse packet) {
|
||||
if (packet.getId() == 98) {
|
||||
this.clientPhase = ClientPhase.HANDSHAKE;
|
||||
this.isResettable = packet.isSuccess();
|
||||
} else if (packet.getId() == 0) {
|
||||
this.clientPhase = ClientPhase.MODLIST;
|
||||
if (modListData == null) {
|
||||
modListData = ByteBufUtil.getBytes(packet.content());
|
||||
}
|
||||
}
|
||||
if (!listenerList.removeIf(id -> id.equals(packet.getId()))) {
|
||||
player.getConnectionInFlight().getConnection().write(packet.retain());
|
||||
|
|
|
|||
|
|
@ -1,144 +0,0 @@
|
|||
package org.adde0109.ambassador.forge;
|
||||
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.player.KickedFromServerEvent;
|
||||
import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.TranslatableComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.adde0109.ambassador.Ambassador;
|
||||
import org.adde0109.ambassador.velocity.VelocityForgeClientConnectionPhase;
|
||||
|
||||
public class ForgeHandshakeHandler {
|
||||
|
||||
private final Ambassador ambassador;
|
||||
|
||||
private final Map<RegisteredServer, ForgeServerConnection>
|
||||
forgeServerConnectionMap = new HashMap<>();
|
||||
private final Map<InetSocketAddress,ForgeConnection> incomingForgeConnections = new HashMap<>();
|
||||
|
||||
private static final ChannelIdentifier LOGIN_WRAPPER_ID = MinecraftChannelIdentifier.create("fml","loginwrapper");
|
||||
|
||||
|
||||
public ForgeHandshakeHandler(Ambassador ambassador) {
|
||||
this.ambassador = ambassador;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public void handleLogin(ConnectedPlayer player, Continuation continuation) {
|
||||
getInitialHandshake(player).whenCompleteAsync((msg,ex) -> {
|
||||
if (ex != null) {
|
||||
ambassador.logger.warn("Forge player, " + player.getUsername() + ", is entering vanilla-mode because of: " + ex.getMessage());
|
||||
}
|
||||
((VelocityForgeClientConnectionPhase) player.getPhase()).handleLogin(player,msg,continuation);
|
||||
}, player.getConnection().eventLoop());
|
||||
}
|
||||
|
||||
private CompletableFuture<ForgeHandshakeUtils.CachedServerHandshake> getInitialHandshake(ConnectedPlayer player) {
|
||||
CompletableFuture<ForgeHandshakeUtils.CachedServerHandshake> future;
|
||||
RegisteredServer initialServer;
|
||||
if((initialServer = ambassador.config.getServer(player.getConnection().getProtocolVersion().getProtocol())) != null) {
|
||||
future = ForgeHandshakeUtils.HandshakeReceiver.downloadHandshake(initialServer);
|
||||
} else {
|
||||
future = CompletableFuture.failedFuture(new Exception("No initial server is specified"));
|
||||
}
|
||||
return future;
|
||||
}
|
||||
|
||||
private void registerForgeConnection(ForgeConnection forgeConnection) {
|
||||
if (forgeConnection != null) {
|
||||
incomingForgeConnections.values().removeIf((c) -> !c.getConnection().isActive());
|
||||
incomingForgeConnections.put(forgeConnection.getConnection().getRemoteAddress(), forgeConnection);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ForgeConnection> getForgeConnection(Player player) {
|
||||
return getForgeConnection(player.getRemoteAddress());
|
||||
}
|
||||
|
||||
private Optional<ForgeConnection> getForgeConnection(InetSocketAddress socketAddress) {
|
||||
incomingForgeConnections.values().removeIf((c) -> !c.getConnection().isActive());
|
||||
return Optional.ofNullable(incomingForgeConnections.get(socketAddress));
|
||||
}
|
||||
|
||||
public int getAmountOfForgeConnections() {
|
||||
return incomingForgeConnections.size();
|
||||
}
|
||||
|
||||
public Optional<ForgeServerConnection> getForgeServerConnection(RegisteredServer registeredServer) {
|
||||
return Optional.ofNullable(forgeServerConnectionMap.get(registeredServer));
|
||||
}
|
||||
|
||||
public void registerForgeServer(RegisteredServer server, ForgeServerConnection forgeServerConnection) {
|
||||
forgeServerConnectionMap.put(server,forgeServerConnection);
|
||||
}
|
||||
public void unRegisterForgeServer(RegisteredServer server) {
|
||||
forgeServerConnectionMap.remove(server);
|
||||
}
|
||||
|
||||
//@Subscribe
|
||||
public void onServerLoginPluginMessageEvent(ServerLoginPluginMessageEvent event, Continuation continuation) {
|
||||
if (!event.getIdentifier().equals(LOGIN_WRAPPER_ID)) {
|
||||
continuation.resume();
|
||||
return;
|
||||
}
|
||||
//Check 2
|
||||
if (getForgeServerConnection(event.getConnection().getServer()).isEmpty()) {
|
||||
registerForgeServer(event.getConnection().getServer(),
|
||||
new ForgeServerConnection(event.getConnection().getServer()));
|
||||
}
|
||||
|
||||
if (incomingForgeConnections.containsKey(event.getConnection().getPlayer().getRemoteAddress())) {
|
||||
incomingForgeConnections.get(event.getConnection().getPlayer().getRemoteAddress())
|
||||
.handleServerHandshakePacket(event,continuation);
|
||||
} else {
|
||||
//This will lead to "multiplayer.disconnect.unexpected_query_response"
|
||||
//and will be handled during KickFromServerEvent.
|
||||
continuation.resume();
|
||||
}
|
||||
}
|
||||
|
||||
//@Subscribe
|
||||
public void onKickedFromServerEvent(KickedFromServerEvent event, Continuation continuation) {
|
||||
Optional<ForgeConnection> forgeConnectionOptional = getForgeConnection(event.getPlayer());
|
||||
if (forgeConnectionOptional.isPresent()) {
|
||||
if (forgeConnectionOptional.get().isForced() && event.getResult() instanceof KickedFromServerEvent.RedirectPlayer) {
|
||||
event.setResult(KickedFromServerEvent.DisconnectPlayer.create(event.getServerKickReason().get()));
|
||||
}
|
||||
} else if (event.getServerKickReason().isPresent()) {
|
||||
Component reason = event.getServerKickReason().get();
|
||||
if (reason instanceof TranslatableComponent)
|
||||
if (((TranslatableComponent) reason).key().equals("multiplayer.disconnect.unexpected_query_response")) {
|
||||
if (getForgeServerConnection(event.getServer()).isPresent()) {
|
||||
//Turns out the server the vanilla client is connecting to is forge. Let's handle the connection error.
|
||||
ambassador.logger.info("Vanilla player {} tried to connect to forge server {}. The connection error can be ignored.",
|
||||
event.getPlayer(),event.getServer().getServerInfo().getName());
|
||||
KickedFromServerEvent.ServerKickResult result = event.getResult();
|
||||
Component component = Component.text("The server you were trying to connect to requires Forge to be installed.", NamedTextColor.RED);
|
||||
if (result instanceof KickedFromServerEvent.DisconnectPlayer) {
|
||||
event.setResult(KickedFromServerEvent.DisconnectPlayer.create(component));
|
||||
} else if (result instanceof KickedFromServerEvent.RedirectPlayer) {
|
||||
RegisteredServer redirectServer = ((KickedFromServerEvent.RedirectPlayer)event.getResult()).getServer();
|
||||
event.setResult(KickedFromServerEvent.RedirectPlayer.create(redirectServer,component));
|
||||
} else if (result instanceof KickedFromServerEvent.Notify) {
|
||||
event.setResult(KickedFromServerEvent.Notify.create(component));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
continuation.resume();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
package org.adde0109.ambassador.forge;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ForgeServerConnection {
|
||||
|
||||
private static final int PACKET_LENGTH_INDEX = 14; //length of "fml:handshake"+1
|
||||
private final RegisteredServer handshakeServer;
|
||||
private ForgeHandshakeUtils.CachedServerHandshake handshake;
|
||||
|
||||
public RegisteredServer getServer() {
|
||||
return handshakeServer;
|
||||
}
|
||||
|
||||
public ForgeServerConnection(RegisteredServer handshakeServer) {
|
||||
this.handshakeServer = handshakeServer;
|
||||
}
|
||||
|
||||
public CompletableFuture<ForgeHandshakeUtils.CachedServerHandshake> getHandshake() {
|
||||
CompletableFuture<ForgeHandshakeUtils.CachedServerHandshake> future;
|
||||
if (handshake == null) {
|
||||
future = ForgeHandshakeUtils.HandshakeReceiver.downloadHandshake(handshakeServer);
|
||||
} else {
|
||||
future = ForgeHandshakeUtils.HandshakeReceiver.downloadHandshake(handshakeServer,handshake);
|
||||
}
|
||||
future.thenAccept(p -> handshake = p);
|
||||
return future;
|
||||
}
|
||||
|
||||
public ServerInfo getServerInfo() {
|
||||
return handshakeServer.getServerInfo();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,217 +0,0 @@
|
|||
package org.adde0109.ambassador.forge;
|
||||
|
||||
import com.velocitypowered.api.event.Continuation;
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
|
||||
import com.velocitypowered.api.util.UuidUtils;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.concurrent.GenericFutureListener;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
import org.adde0109.ambassador.Ambassador;
|
||||
|
||||
import org.adde0109.ambassador.AmbassadorConfig;
|
||||
import org.apache.commons.collections4.map.PassiveExpiringMap;
|
||||
|
||||
public class ForgeServerSwitchHandler {
|
||||
private final Ambassador ambassador;
|
||||
public final PassiveExpiringMap<String,ForgeServerConnection> reSyncMap;
|
||||
|
||||
public ForgeServerSwitchHandler(Ambassador ambassador) {
|
||||
this.ambassador = ambassador;
|
||||
this.reSyncMap = new PassiveExpiringMap<>(ambassador.config.getReSyncTimeout(),TimeUnit.SECONDS);
|
||||
}
|
||||
|
||||
|
||||
//@Subscribe(order = PostOrder.LAST)
|
||||
public void onServerPreConnectEvent(ServerPreConnectEvent event, Continuation continuation) {
|
||||
if (!event.getResult().isAllowed()) {
|
||||
continuation.resume();
|
||||
return;
|
||||
}
|
||||
Optional<ForgeServerConnection> forgeServerConnectionOptional = ambassador.forgeHandshakeHandler.getForgeServerConnection(event.getOriginalServer());
|
||||
Optional<ForgeConnection> forgeConnectionOptional = ambassador.forgeHandshakeHandler.getForgeConnection(event.getPlayer());
|
||||
if (forgeConnectionOptional.isPresent()) {
|
||||
ForgeConnection forgeConnection = forgeConnectionOptional.get();
|
||||
if (forgeConnection.isResettable()) {
|
||||
continuation.resume();
|
||||
return;
|
||||
}
|
||||
ForgeServerConnection forgeServerConnection = forgeServerConnectionOptional.orElseGet(() -> new ForgeServerConnection(event.getOriginalServer()));
|
||||
forgeServerConnection.getHandshake().whenComplete((msg, ex) -> {
|
||||
if (ex != null) {
|
||||
//The server was forge but aren't right now. Or it's just offline.
|
||||
if (ex instanceof ForgeHandshakeUtils.HandshakeReceiver.HandshakeNotAvailableException) {
|
||||
//It's not running ambassador, so it should be unregistered.
|
||||
if (forgeServerConnectionOptional.isPresent())
|
||||
ambassador.forgeHandshakeHandler.unRegisterForgeServer(forgeServerConnection.getServer());
|
||||
}
|
||||
} else {
|
||||
//If the server just got discovered, register it.
|
||||
if (forgeServerConnectionOptional.isEmpty())
|
||||
ambassador.forgeHandshakeHandler.registerForgeServer(event.getOriginalServer(),forgeServerConnection);
|
||||
|
||||
//To make legacy forwarding work
|
||||
List<GameProfile.Property> properties = new ArrayList<>(event.getPlayer().getGameProfileProperties());
|
||||
properties.add(new GameProfile.Property("extraData", "\1FML2\1",""));
|
||||
event.getPlayer().setGameProfileProperties(properties);
|
||||
|
||||
if (ambassador.config.reSyncOptionForge() != AmbassadorConfig.reSyncOption.NEVER) {
|
||||
if (forgeConnection.getTransmittedHandshake().isEmpty() || !msg.equals(forgeConnection.getTransmittedHandshake().get())) {
|
||||
event.setResult(ServerPreConnectEvent.ServerResult.denied());
|
||||
kickReSync(event.getPlayer(), forgeServerConnection);
|
||||
}
|
||||
}
|
||||
}
|
||||
continuation.resume();
|
||||
});
|
||||
} else if (forgeServerConnectionOptional.isPresent()) {
|
||||
//If vanilla tries to connect to a server we know is forge
|
||||
event.setResult(ServerPreConnectEvent.ServerResult.denied());
|
||||
event.getPlayer().sendMessage(Component.text("This server requires Forge!", NamedTextColor.RED));
|
||||
continuation.resume();
|
||||
} else {
|
||||
//The server is not known to us.
|
||||
continuation.resume();
|
||||
}
|
||||
}
|
||||
private void kickReSync(Player player, ForgeServerConnection forgeServerConnection){
|
||||
ambassador.logger.info("Kicking {} because of re-sync needed", player);
|
||||
player.disconnect(Component.text("Please reconnect"));
|
||||
reSyncMap.put(player.getUsername(),forgeServerConnection);
|
||||
}
|
||||
|
||||
//@Subscribe
|
||||
public void onServerConnectedEvent(ServerConnectedEvent event, Continuation continuation) {
|
||||
ConnectedPlayer player = ((ConnectedPlayer) event.getPlayer());
|
||||
Optional<ForgeConnection> forgeConnection = ambassador.forgeHandshakeHandler.getForgeConnection(player);
|
||||
if (forgeConnection.isEmpty() || !forgeConnection.get().isResettable()) {
|
||||
//Don't bother unless the client can be reset.
|
||||
continuation.resume();
|
||||
return;
|
||||
}
|
||||
Optional<ForgeServerConnection> forgeServerConnection = ambassador.forgeHandshakeHandler.getForgeServerConnection(event.getServer());
|
||||
if (forgeServerConnection.isPresent() && event.getPreviousServer().isPresent()) {
|
||||
Future<ForgeHandshakeUtils.CachedServerHandshake> handshakeFuture = forgeServerConnection.get().getHandshake();
|
||||
player.getConnection().eventLoop().submit(() -> {
|
||||
reSync(player,handshakeFuture,continuation);
|
||||
});
|
||||
} else {
|
||||
continuation.resume();
|
||||
}
|
||||
}
|
||||
|
||||
private void reSync(ConnectedPlayer player, Future<ForgeHandshakeUtils.CachedServerHandshake> handshakeFuture, Continuation continuation) {
|
||||
MinecraftConnection connection = player.getConnection();
|
||||
connection.setSessionHandler(new ReSyncHandler(player,handshakeFuture,continuation));
|
||||
connection.write(new PluginMessage("fml:handshake", Unpooled.wrappedBuffer(ForgeHandshakeUtils.generateResetPacket())));
|
||||
connection.setState(StateRegistry.LOGIN);
|
||||
}
|
||||
private class ReSyncHandler implements MinecraftSessionHandler {
|
||||
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
|
||||
private final MinecraftConnection connection;
|
||||
private final Future<ForgeHandshakeUtils.CachedServerHandshake> handshakeFuture;
|
||||
private final MinecraftSessionHandler originalHandler;
|
||||
private final Continuation continuation;
|
||||
|
||||
private List<Integer> inTransit = new ArrayList<>();
|
||||
|
||||
ReSyncHandler(ConnectedPlayer player, Future<ForgeHandshakeUtils.CachedServerHandshake> handshakeFuture, Continuation continuation) {
|
||||
this.player = player;
|
||||
this.connection = player.getConnection();
|
||||
this.handshakeFuture = handshakeFuture;
|
||||
this.originalHandler = this.connection.getSessionHandler();
|
||||
this.continuation = continuation;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean handle(LoginPluginResponse packet) {
|
||||
if (!inTransit.removeIf((s) -> s == packet.getId())) {
|
||||
if (packet.getId() == 98) {
|
||||
ForgeHandshakeUtils.CachedServerHandshake handshake;
|
||||
try {
|
||||
handshake = handshakeFuture.get();
|
||||
} catch (Exception e) {
|
||||
return true;
|
||||
}
|
||||
sendHandshake(connection, handshake);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
} else if (inTransit.isEmpty()) {
|
||||
complete();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void sendHandshake(MinecraftConnection connection, ForgeHandshakeUtils.CachedServerHandshake handshake) {
|
||||
int transactionId = 1;
|
||||
connection.delayedWrite(new LoginPluginMessage(transactionId, "fml:loginwrapper", Unpooled.wrappedBuffer(handshake.modListPacket)));
|
||||
inTransit.add(transactionId);
|
||||
for (byte[] data : handshake.otherPackets) {
|
||||
transactionId++;
|
||||
connection.delayedWrite(new LoginPluginMessage(transactionId, "fml:loginwrapper", Unpooled.wrappedBuffer(data)));
|
||||
inTransit.add(transactionId);
|
||||
}
|
||||
connection.flush();
|
||||
}
|
||||
|
||||
private void complete() {
|
||||
VelocityConfiguration configuration = (VelocityConfiguration) ambassador.server.getConfiguration();
|
||||
UUID playerUniqueId = player.getUniqueId();
|
||||
if (configuration.getPlayerInfoForwardingMode() == PlayerInfoForwarding.NONE) {
|
||||
playerUniqueId = UuidUtils.generateOfflinePlayerUuid(player.getUsername());
|
||||
}
|
||||
ServerLoginSuccess success = new ServerLoginSuccess();
|
||||
success.setUsername(player.getUsername());
|
||||
success.setUuid(playerUniqueId);
|
||||
connection.write(success);
|
||||
connection.setState(StateRegistry.PLAY);
|
||||
connection.setSessionHandler(originalHandler);
|
||||
continuation.resume();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
originalHandler.handleUnknown(buf);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
originalHandler.disconnected();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -36,7 +36,7 @@ public class VelocityEventHandler {
|
|||
continuation.resume();
|
||||
return;
|
||||
}
|
||||
ambassador.forgeHandshakeHandler.handleLogin(player,continuation);
|
||||
phase.handleLogin(player,null,continuation);
|
||||
}
|
||||
|
||||
/*@Subscribe
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import java.util.List;
|
|||
|
||||
public class VelocityForgeBackendConnectionPhase implements BackendConnectionPhase {
|
||||
|
||||
private final List<LoginPluginMessage> queuedHandshakePackets = new ArrayList<>();
|
||||
private List<LoginPluginMessage> queuedHandshakePackets = new ArrayList<>();
|
||||
|
||||
public VelocityForgeBackendConnectionPhase() {
|
||||
}
|
||||
|
|
@ -34,7 +34,9 @@ public class VelocityForgeBackendConnectionPhase implements BackendConnectionPha
|
|||
player.getConnection().delayedWrite(msg);
|
||||
}
|
||||
player.getConnection().flush();
|
||||
queuedHandshakePackets = null;
|
||||
});
|
||||
queuedHandshakePackets = new ArrayList<>();
|
||||
queuedHandshakePackets.add(message);
|
||||
} else if (clientPhase.clientPhase != null) {
|
||||
player.getConnection().write(message);
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
config-version = 1
|
||||
# The player can only be synced to ONE forge server at a time.
|
||||
# Resync will always happen if the player isn't synced to any server and is switching to a forge server.
|
||||
[ReSync]
|
||||
# In seconds. How much time the player has to reconnect before the resync cancels.
|
||||
resync-timeout = 30
|
||||
|
||||
# Possible values: "Always", "Never".
|
||||
# Always: Always kicks the player while switching servers. The safest.
|
||||
# Never: Switches server without kicking/resyncing the player. Can cause client crashes depending on the mods.
|
||||
|
||||
# When the player is connecting to a forge server while being synced to another.
|
||||
# Default: "always"
|
||||
resync-forge-to-forge = "always"
|
||||
# When the player is connecting to a vanilla server and is synced to a forge server.
|
||||
# Default "never". "always" not recommended for servers with vanilla hubs/lobbies.
|
||||
unsync-forge-to-vanilla = "never"
|
||||
|
||||
# Maybe you want to have one 1.16.5 modpack-server and one 1.18.2 modpack-server behind Velocity, in order for Ambassador to tell the
|
||||
# diffrence between modpacks on the connecting client, the plugin looks at the client's protocol version.
|
||||
|
||||
# You may add more diffrentiators, just make sure they have diffrent protocol versions.
|
||||
[Differentiators]
|
||||
|
||||
# Protocol version - 1.18.2
|
||||
[Differentiators.758]
|
||||
# The server that players will initially sync to when connecting to the proxy.
|
||||
# When left empty; Players will be able to join but will need to resync when joining connecting to a forge server.
|
||||
# The name should be the same as specified in the "velocity.toml" config, e.g. "lobby".
|
||||
default-forge-server = ""
|
||||
|
||||
# Some modpacks are not compatible with vanilla servers.
|
||||
# To make sure they don't get connected to one upon login (like a vanilla lobby), change this to true.
|
||||
forced = false
|
||||
|
||||
# Protocol version - 1.16.5
|
||||
[Differentiators.754]
|
||||
default-forge-server = ""
|
||||
forced = false
|
||||
Loading…
Reference in New Issue
Block a user