Mod detection refactoring #509
@ -71,6 +71,15 @@ repositories {
|
||||
}
|
||||
}
|
||||
|
||||
shadowJar {
|
||||
exclude 'META-INF/*'
|
||||
//https://imperceptiblethoughts.com/shadow/configuration/minimizing/
|
||||
minimize {
|
||||
exclude project(':')
|
||||
}
|
||||
duplicatesStrategy DuplicatesStrategy.INCLUDE
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compileOnly 'org.projectlombok:lombok:1.18.22'
|
||||
testCompileOnly 'org.projectlombok:lombok:1.18.22'
|
||||
|
@ -92,6 +92,7 @@ public class BungeeCore extends Plugin {
|
||||
});
|
||||
ProxyServer.getInstance().getScheduler().schedule(this, TabCompletionCache::invalidateOldEntries, 1, 1, TimeUnit.SECONDS);
|
||||
|
||||
new Hostname();
|
||||
new ServerListPing();
|
||||
new PluginMessage();
|
||||
new Schematica();
|
||||
@ -104,7 +105,7 @@ public class BungeeCore extends Plugin {
|
||||
new ChatListener();
|
||||
new BanListener();
|
||||
new CheckListener();
|
||||
new SubserverProtocolFixer();
|
||||
new IPSanitizer();
|
||||
|
||||
local = new Node.LocalNode();
|
||||
if(MAIN_SERVER) {
|
||||
|
@ -22,7 +22,10 @@ package de.steamwar.bungeecore;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
|
||||
import java.io.*;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.file.Files;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
@ -31,6 +34,7 @@ import java.util.logging.Level;
|
||||
|
||||
public abstract class Node {
|
||||
|
||||
//-Xquickstart Langzeitperformance testen!
|
||||
private static final List<String> OPENJ9_ARGS = Arrays.asList(
|
||||
"-XX:+EnableCRIUSupport", "-XX:-CRIURestoreNonPortableMode",
|
||||
"-Xgc:excessiveGCratio=80", "-Xdisableexplicitgc", "-Xnoclassgc", "-Xmos128M", "-Xmns48M", "-XX:+ExitOnOutOfMemoryError", // initial heap half values of memory observed by 1.19 spectate server
|
||||
@ -118,6 +122,8 @@ public abstract class Node {
|
||||
cmd.add(worldDir);
|
||||
cmd.add("--level-name");
|
||||
cmd.add(levelName);
|
||||
cmd.add("--plugins");
|
||||
cmd.add("/home/lixfel/bpl");
|
||||
cmd.add("--port");
|
||||
cmd.add(String.valueOf(port));
|
||||
cmd.add("nogui");
|
||||
|
@ -19,52 +19,41 @@
|
||||
|
||||
package de.steamwar.bungeecore.listeners;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.protocol.Property;
|
||||
import net.md_5.bungee.util.AddressUtil;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class SubserverProtocolFixer extends BasicListener {
|
||||
public class IPSanitizer extends BasicListener {
|
||||
|
||||
private static final Field initialHandlerCh;
|
||||
static {
|
||||
try {
|
||||
initialHandlerCh = InitialHandler.class.getDeclaredField("ch");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new SecurityException("Could not initialize Reflection", e);
|
||||
}
|
||||
initialHandlerCh.setAccessible(true);
|
||||
}
|
||||
|
||||
public static ChannelWrapper getChannelWrapper(InitialHandler handler) {
|
||||
try {
|
||||
return (ChannelWrapper) initialHandlerCh.get(handler);
|
||||
} catch (IllegalAccessException e) {
|
||||
throw new SecurityException("Could not get channel wrapper", e);
|
||||
}
|
||||
}
|
||||
|
||||
private final InetSocketAddress inetSocketAddress = new InetSocketAddress("127.127.127.127", 25565);
|
||||
|
||||
private Field field;
|
||||
|
||||
{
|
||||
try {
|
||||
field = InitialHandler.class.getDeclaredField("extraDataInHandshake");
|
||||
field.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
BungeeCord.getInstance().getLogger().log(Level.SEVERE, e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void loginEvent(LoginEvent e) {
|
||||
InitialHandler initialHandler = ((InitialHandler) e.getConnection());
|
||||
|
||||
String undashedUUID = initialHandler.getUniqueId().toString().replace("-", "");
|
||||
String extraData = "\00" + AddressUtil.sanitizeAddress(inetSocketAddress) + "\00" + undashedUUID;
|
||||
|
||||
LoginResult result = initialHandler.getLoginProfile();
|
||||
if (result != null) {
|
||||
Property[] properties = result.getProperties();
|
||||
if (properties.length > 0) {
|
||||
extraData += "\00" + BungeeCord.getInstance().gson.toJson(properties);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
field.set(initialHandler, extraData);
|
||||
} catch (IllegalAccessException ex) {
|
||||
BungeeCord.getInstance().getLogger().log(Level.SEVERE, ex.getMessage(), ex);
|
||||
}
|
||||
BungeeCore.get().getLogger().log(Level.INFO, e.getConnection().getSocketAddress() + " has logged in with user name " + e.getConnection().getName());
|
||||
getChannelWrapper((InitialHandler) e.getConnection()).setRemoteAddress(inetSocketAddress);
|
||||
}
|
||||
}
|
@ -28,24 +28,29 @@ import de.steamwar.messages.ChatSender;
|
||||
import de.steamwar.network.packets.NetworkPacket;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufAllocator;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class PluginMessage extends BasicListener {
|
||||
|
||||
public static void send(ProxiedPlayer player, String legacyChannel, String channel, byte[] data) {
|
||||
// 1.12 format change
|
||||
player.sendData(player.getPendingConnection().getVersion() > 340 ? channel : legacyChannel, data);
|
||||
send(player, player.getPendingConnection().getVersion() > 340 ? channel : legacyChannel, data);
|
||||
}
|
||||
|
||||
public static void send(ProxiedPlayer player, String channel, byte[] data) {
|
||||
if(((InitialHandler)player.getPendingConnection()).getRegisteredChannels().contains(channel))
|
||||
player.sendData(channel, data);
|
||||
}
|
||||
|
||||
//TODO change to warning level
|
||||
@ -55,6 +60,8 @@ public class PluginMessage extends BasicListener {
|
||||
|
||||
private final Lunar lunar = new Lunar();
|
||||
|
||||
private final Set<String> knownBrands = new HashSet<>();
|
||||
private final Set<String> knownChannels = new HashSet<>();
|
||||
private final Map<String, Parser> handlers = new HashMap<>();
|
||||
|
||||
public PluginMessage() {
|
||||
@ -63,8 +70,31 @@ public class PluginMessage extends BasicListener {
|
||||
FabricModSender fabricModSender = new FabricModSender();
|
||||
WorldDownloader wdl = new WorldDownloader();
|
||||
|
||||
register("REGISTER", false, directional(PASS_THROUGH, this::clientRegistersChannel));
|
||||
register("minecraft:register", false, directional(PASS_THROUGH, this::clientRegistersChannel));
|
||||
knownBrands.add("vanilla");
|
||||
knownBrands.add("fabric");
|
||||
knownBrands.add("forge"); //Forge registers all channels the server registers
|
||||
|
||||
knownChannels.add("fabric:container/open");
|
||||
knownChannels.add("fabric:registry/sync/direct");
|
||||
knownChannels.add("fabric-screen-handler-api-v1:open_screen");
|
||||
|
||||
knownChannels.add(FML.CHANNEL);
|
||||
knownChannels.add("fml:loginwrapper");
|
||||
knownChannels.add("fml:handshake");
|
||||
knownChannels.add("fml:play");
|
||||
knownChannels.add("forge:tier_sorting");
|
||||
knownChannels.add("forge:split");
|
||||
knownChannels.add("forge:login");
|
||||
knownChannels.add("forge:handshake");
|
||||
|
||||
knownChannels.add("Replay|Restrict");
|
||||
knownChannels.add("replaymod:restrict");
|
||||
knownChannels.add("WDL|CONTROL");
|
||||
knownChannels.add("wdl:control");
|
||||
knownChannels.add("worldedit:cui");
|
||||
|
||||
register("REGISTER", false, directional(this::serverRegistersChannel, this::clientRegistersChannel));
|
||||
register("minecraft:register", false, directional(this::serverRegistersChannel, this::clientRegistersChannel));
|
||||
|
||||
register("BungeeCord", false, onlySWSource(PASS_THROUGH));
|
||||
register("bungeecord:main", false, onlySWSource(PASS_THROUGH));
|
||||
@ -73,6 +103,7 @@ public class PluginMessage extends BasicListener {
|
||||
|
||||
register("sw:script_syntax", false, directional(onlySWSource(PASS_THROUGH), UNKNOWN));
|
||||
register("sw:bridge", false, directional(onlySWSource(async(event -> NetworkPacket.handle(new ServerMetaInfo(((Server) event.getSender()).getInfo()), event.getData()))), UNKNOWN));
|
||||
register("worldedit:cui", false, PASS_THROUGH);
|
||||
register("fabricmodsender:mods", true, directional(UNKNOWN, fabricModSender::handlePluginMessage));
|
||||
|
||||
register("WDL|REQUEST", false, DROP);
|
||||
@ -83,7 +114,7 @@ public class PluginMessage extends BasicListener {
|
||||
register(ApolloManager.PLUGIN_MESSAGE_CHANNEL, true, async(lunar::handlePluginMessage));
|
||||
register("LMC", true, directional(UNKNOWN, async(labyMod::handlePluginMessage)));
|
||||
register("labymod3:main", true, directional(UNKNOWN, async(labyMod::handlePluginMessage)));
|
||||
register(FML.CHANNEL, true, directional(UNKNOWN, fml::handlePluginMessage));
|
||||
register(FML.CHANNEL, true, directional(UNKNOWN, async(fml::handlePluginMessage)));
|
||||
|
||||
//litematica/malilib https://github.com/maruohon/litematica/issues/75 https://github.com/maruohon/malilib/blob/liteloader_1.12.2/src/main/java/malilib/network/message/ConfigLockPacketHandler.java#L65
|
||||
}
|
||||
@ -114,7 +145,7 @@ public class PluginMessage extends BasicListener {
|
||||
if(channel.equals(ApolloManager.PLUGIN_MESSAGE_CHANNEL))
|
||||
lunar.sendRestrictions(player);
|
||||
|
||||
if(!handlers.containsKey(channel))
|
||||
if(!knownChannels.contains(channel))
|
||||
//TODO change to warning level
|
||||
BungeeCore.get().getLogger().log(Level.INFO, () -> player.getName() + " registered unknown channel " + channel);
|
||||
}
|
||||
@ -122,15 +153,22 @@ public class PluginMessage extends BasicListener {
|
||||
PASS_THROUGH.handle(event);
|
||||
}
|
||||
|
||||
private void serverRegistersChannel(PluginMessageEvent event) {
|
||||
ProxiedPlayer player = (ProxiedPlayer)event.getReceiver();
|
||||
|
||||
List<String> channels = new ArrayList<>(Arrays.asList(new String(event.getData()).split("\0")));
|
||||
channels.removeIf(channel -> channel.equals("sw:bridge"));
|
||||
player.sendData((player).getPendingConnection().getVersion() > 340 ? "minecraft:register" : "REGISTER", String.join("\0", channels).getBytes());
|
||||
}
|
||||
|
||||
private void userBrand(PluginMessageEvent event) {
|
||||
ProxiedPlayer player = (ProxiedPlayer) event.getSender();
|
||||
String brand = new String(event.getData());
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(event.getData());
|
||||
String brand = DefinedPacket.readString(buf);
|
||||
|
||||
if(brand.equals("vanilla")) {
|
||||
// nothing to do
|
||||
} else if(brand.startsWith("lunarclient:")) {
|
||||
if(brand.startsWith("lunarclient:")) {
|
||||
lunar.sendRestrictions(player);
|
||||
} else {
|
||||
} else if(!knownBrands.contains(brand)) {
|
||||
//TODO change to warning level
|
||||
BungeeCore.get().getLogger().log(Level.INFO, () -> player.getName() + " joined with unknown brand " + brand);
|
||||
}
|
||||
|
@ -25,6 +25,7 @@ import de.steamwar.sql.Mod;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.PluginMessageEvent;
|
||||
import net.md_5.bungee.api.event.PostLoginEvent;
|
||||
@ -38,6 +39,10 @@ import java.util.concurrent.TimeUnit;
|
||||
public class FML extends BasicListener {
|
||||
// https://wiki.vg/Minecraft_Forge_Handshake#FML_protocol_.281.7_-_1.12.29
|
||||
|
||||
public static boolean isFML(PendingConnection connection, String type) {
|
||||
return ((InitialHandler)connection).getExtraDataInHandshake().equals("\0" + type);
|
||||
}
|
||||
|
||||
public static final String CHANNEL = "FML|HS";
|
||||
private final byte[] helloPacket = new byte[]{
|
||||
/* Packet type: ServerHello */ 0,
|
||||
@ -58,9 +63,8 @@ public class FML extends BasicListener {
|
||||
}
|
||||
}
|
||||
|
||||
if(((InitialHandler)player.getPendingConnection()).getHandshake().getHost().endsWith("\0FML\0")) {
|
||||
if(isFML(player.getPendingConnection(), "FML\0"))
|
||||
player.sendData(CHANNEL, helloPacket);
|
||||
}
|
||||
}
|
||||
|
||||
public void handlePluginMessage(PluginMessageEvent event) {
|
||||
|
@ -21,6 +21,7 @@ package de.steamwar.bungeecore.mods;
|
||||
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import de.steamwar.bungeecore.listeners.BasicListener;
|
||||
import de.steamwar.bungeecore.listeners.IPSanitizer;
|
||||
import de.steamwar.sql.Mod;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
@ -29,67 +30,91 @@ import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.LoginEvent;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.netty.HandlerBoss;
|
||||
import net.md_5.bungee.netty.PacketHandler;
|
||||
import net.md_5.bungee.protocol.DefinedPacket;
|
||||
import net.md_5.bungee.protocol.packet.LoginPayloadRequest;
|
||||
import net.md_5.bungee.protocol.packet.LoginPayloadResponse;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class FML2 extends BasicListener {
|
||||
// https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29
|
||||
// FML2: https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29
|
||||
// FML3: https://github.com/adde0109/Ambassador/tree/non-api/src/main/java/org/adde0109/ambassador/forge
|
||||
|
||||
public static boolean isFML2(PendingConnection connection) {
|
||||
return ((InitialHandler)connection).getHandshake().getHost().endsWith("\0FML2\0");
|
||||
}
|
||||
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/NetworkInitialization.java
|
||||
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/ForgePacketHandler.java
|
||||
// FORGE: https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/packets/ModVersions.java
|
||||
|
||||
private final Field initialHandlerCh;
|
||||
private final byte[] fml2ModListPacket;
|
||||
private final byte[] fml3ModListPacket;
|
||||
private final byte[] forgeModListPacket;
|
||||
|
||||
public FML2() {
|
||||
try {
|
||||
initialHandlerCh = InitialHandler.class.getDeclaredField("ch");
|
||||
} catch (NoSuchFieldException e) {
|
||||
throw new SecurityException("Could not initialize Reflection", e);
|
||||
}
|
||||
initialHandlerCh.setAccessible(true);
|
||||
}
|
||||
fml2ModListPacket = generateModListPacket(false);
|
||||
fml3ModListPacket = generateModListPacket(true);
|
||||
|
||||
//TODO current identifier is \0FORGE and fmlNetworkVersion 0 ?
|
||||
//TODO links for current implementation (if current checker not working)
|
||||
//https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/ServerStatusPing.java
|
||||
//https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/NetworkContext.java#L26
|
||||
//https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/tasks/ForgeNetworkConfigurationHandler.java#L20
|
||||
//https://github.com/MinecraftForge/MinecraftForge/blob/1.20.x/src/main/java/net/minecraftforge/network/NetworkRegistry.java#L177
|
||||
ByteBuf packet = Unpooled.buffer();
|
||||
packet.writeByte(1); // Mod list packet
|
||||
DefinedPacket.writeVarInt(0, packet); // Mod amount
|
||||
|
||||
ByteBuf wrapper = Unpooled.buffer();
|
||||
wrapper.writeByte(0); // Login wrapper packet
|
||||
DefinedPacket.writeString("forge:handshake", wrapper);
|
||||
DefinedPacket.writeVarInt(packet.readableBytes(), wrapper);
|
||||
wrapper.writeBytes(packet);
|
||||
|
||||
forgeModListPacket = new byte[wrapper.readableBytes()];
|
||||
wrapper.readBytes(forgeModListPacket);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLogin(LoginEvent event) {
|
||||
PendingConnection connection = event.getConnection();
|
||||
|
||||
if(!isFML2(connection))
|
||||
boolean fml2 = FML.isFML(connection, "FML2\0");
|
||||
boolean fml3 = FML.isFML(connection, "FML3\0");
|
||||
boolean forge = FML.isFML(connection, "FORGE");
|
||||
if(!fml2 && !fml3 && !forge)
|
||||
return;
|
||||
|
||||
try {
|
||||
((ChannelWrapper) initialHandlerCh.get(connection)).getHandle().pipeline().get(HandlerBoss.class).setHandler(new FML2LoginHandler(event));
|
||||
} catch (IllegalAccessException e) {
|
||||
BungeeCore.get().getLogger().log(Level.SEVERE, "Could not get Channel", e);
|
||||
return;
|
||||
}
|
||||
IPSanitizer.getChannelWrapper((InitialHandler) connection).getHandle().pipeline().get(HandlerBoss.class).setHandler(new FML2LoginHandler(event));
|
||||
|
||||
event.registerIntent(BungeeCore.get());
|
||||
connection.unsafe().sendPacket(new LoginPayloadRequest(1, "fml:loginwrapper", new byte[] {
|
||||
/* fml:handshake */ 13,102,109,108,58,104,97,110,100,115,104,97,107,101,
|
||||
/* Packet length */ 4,
|
||||
/* Type mod list */ 1,
|
||||
/* Mod count */ 0,
|
||||
/* Channel count */ 0,
|
||||
/* Registry count */ 0
|
||||
}));
|
||||
if(forge)
|
||||
connection.unsafe().sendPacket(new LoginPayloadRequest(1, "forge:login", forgeModListPacket));
|
||||
else
|
||||
connection.unsafe().sendPacket(new LoginPayloadRequest(1, "fml:loginwrapper", fml3 ? fml3ModListPacket : fml2ModListPacket));
|
||||
}
|
||||
|
||||
private byte[] generateModListPacket(boolean fml3) {
|
||||
ByteBuf packet = Unpooled.buffer();
|
||||
packet.writeByte(1); // Mod list packet
|
||||
DefinedPacket.writeVarInt(0, packet); // Mod amount
|
||||
|
||||
if(fml3) {
|
||||
DefinedPacket.writeVarInt(1, packet); // Channel amount
|
||||
DefinedPacket.writeString("forge:tier_sorting", packet);
|
||||
DefinedPacket.writeString("1.0", packet);
|
||||
} else {
|
||||
DefinedPacket.writeVarInt(0, packet); // Channel amount
|
||||
}
|
||||
|
||||
DefinedPacket.writeVarInt(0, packet); // Registries amount
|
||||
if(fml3)
|
||||
DefinedPacket.writeVarInt(0, packet); // DataPacks amount
|
||||
|
||||
ByteBuf wrapper = Unpooled.buffer();
|
||||
DefinedPacket.writeString("fml:handshake", wrapper);
|
||||
DefinedPacket.writeVarInt(packet.readableBytes(), wrapper);
|
||||
wrapper.writeBytes(packet);
|
||||
|
||||
byte[] data = new byte[wrapper.readableBytes()];
|
||||
wrapper.readBytes(data);
|
||||
return data;
|
||||
}
|
||||
|
||||
private static class FML2LoginHandler extends PacketHandler {
|
||||
@ -101,40 +126,51 @@ public class FML2 extends BasicListener {
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "SteamWar FML2 Handler";
|
||||
return "SteamWar Forge Handler";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(LoginPayloadResponse response) {
|
||||
boolean forge = FML.isFML(event.getConnection(), "FORGE");
|
||||
byte[] data = response.getData();
|
||||
if(data == null) {
|
||||
abort(response, "Not FML2 client");
|
||||
abort(response, "Not FML2/3 client");
|
||||
return;
|
||||
}
|
||||
|
||||
ByteBuf buf = Unpooled.wrappedBuffer(data);
|
||||
if(!DefinedPacket.readString(buf).equals("fml:handshake")) {
|
||||
abort(response, "Not FML2 handshake response");
|
||||
if(forge && buf.readByte() != 0) {
|
||||
abort(response, "Not FORGE login wrapper");
|
||||
return;
|
||||
}
|
||||
|
||||
int packetLength = DefinedPacket.readVarInt(buf);
|
||||
if(packetLength != buf.readableBytes()) {
|
||||
abort(response, "FML2 packet size mismatch");
|
||||
if(!DefinedPacket.readString(buf).equals(forge ? "forge:handshake" : "fml:handshake")) {
|
||||
abort(response, "Not FML2/3/FORGE handshake response");
|
||||
return;
|
||||
}
|
||||
|
||||
if(DefinedPacket.readVarInt(buf) != /* Mod List Reply */ 2) {
|
||||
abort(response, "FML2 no mod list reply");
|
||||
if(DefinedPacket.readVarInt(buf) != buf.readableBytes()) {
|
||||
abort(response, "FML2/3/FORGE packet size mismatch");
|
||||
return;
|
||||
}
|
||||
|
||||
if(DefinedPacket.readVarInt(buf) != (forge ? /* Mod Versions */ 1 : /* Mod List Reply */ 2)) {
|
||||
abort(response, "Not FML2/3/FORGE mod list reply");
|
||||
return;
|
||||
}
|
||||
|
||||
List<Mod> mods = new ArrayList<>();
|
||||
|
||||
int modCount = DefinedPacket.readVarInt(buf);
|
||||
for(int i = 0; i < modCount; i++)
|
||||
for(int i = 0; i < modCount; i++) {
|
||||
mods.add(Mod.getOrCreate(DefinedPacket.readString(buf), Mod.Platform.FORGE));
|
||||
|
||||
if(forge) {
|
||||
DefinedPacket.readString(buf); // Human readable name
|
||||
DefinedPacket.readString(buf); // Version
|
||||
}
|
||||
}
|
||||
|
||||
if(!ModUtils.handleMods(event.getConnection().getUniqueId(), Locale.getDefault(), event::setReason, mods))
|
||||
event.setCancelled(true);
|
||||
|
||||
|
58
src/de/steamwar/bungeecore/mods/Hostname.java
Normale Datei
58
src/de/steamwar/bungeecore/mods/Hostname.java
Normale Datei
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* This file is a part of the SteamWar software.
|
||||
*
|
||||
* Copyright (C) 2024 SteamWar.de-Serverteam
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU Affero General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU Affero General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package de.steamwar.bungeecore.mods;
|
||||
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import de.steamwar.bungeecore.listeners.BasicListener;
|
||||
import net.md_5.bungee.api.event.PlayerHandshakeEvent;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Hostname extends BasicListener {
|
||||
|
||||
private final Set<String> knownHostnames = new HashSet<>();
|
||||
private final Set<String> knownExtraData = new HashSet<>();
|
||||
|
||||
public Hostname() {
|
||||
knownHostnames.add("steamwar.de");
|
||||
knownHostnames.add("78.31.71.136");
|
||||
|
||||
knownExtraData.add("");
|
||||
knownExtraData.add("\0FML\0");
|
||||
knownExtraData.add("\0FML2\0");
|
||||
knownExtraData.add("\0FML3\0");
|
||||
knownExtraData.add("\0FORGE");
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onHandshake(PlayerHandshakeEvent event) {
|
||||
String extraDataInHandshake = ((InitialHandler) event.getConnection()).getExtraDataInHandshake();
|
||||
if (!knownHostnames.contains(event.getHandshake().getHost().toLowerCase())) {
|
||||
BungeeCore.get().getLogger().log(Level.WARNING, () -> event.getConnection().getSocketAddress() + " connected with unknown hostname " + event.getHandshake() + " " + extraDataInHandshake);
|
||||
} else if (!knownExtraData.contains(extraDataInHandshake)) {
|
||||
BungeeCore.get().getLogger().log(Level.WARNING, () -> event.getConnection().getSocketAddress() + " connected with unknown extra data " + event.getHandshake() + " " + extraDataInHandshake);
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -20,9 +20,13 @@
|
||||
package de.steamwar.bungeecore.mods;
|
||||
|
||||
import de.steamwar.bungeecore.Bauserver;
|
||||
import de.steamwar.bungeecore.Builderserver;
|
||||
import de.steamwar.bungeecore.BungeeCore;
|
||||
import de.steamwar.bungeecore.Subserver;
|
||||
import de.steamwar.bungeecore.listeners.BasicListener;
|
||||
import de.steamwar.bungeecore.listeners.PluginMessage;
|
||||
import net.md_5.bungee.api.ProxyServer;
|
||||
import net.md_5.bungee.api.config.ServerInfo;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.ServerSwitchEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
@ -34,38 +38,36 @@ import java.util.Arrays;
|
||||
public class ReplayMod extends BasicListener {
|
||||
// https://gist.github.com/Johni0702/2547c463e51f65f312cb
|
||||
// https://github.com/ReplayMod/replay-restrictions/blob/master/bungeecord/src/main/java/de/johni0702/replay/restrictions/BungeeCordPlugin.java
|
||||
// https://github.com/ReplayMod/ReplayMod/blob/stable/src/main/java/com/replaymod/core/utils/Restrictions.java
|
||||
|
||||
private final byte[] restrict;
|
||||
private final byte[] unlock;
|
||||
|
||||
public ReplayMod() {
|
||||
restrict = generatePacket(true);
|
||||
unlock = generatePacket(false);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(ServerSwitchEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
Subserver server = Subserver.getSubserver(player.getServer().getInfo());
|
||||
|
||||
PluginMessage.send(
|
||||
player, "Replay|Restrict", "replaymod:restrict",
|
||||
(server instanceof Bauserver && ((Bauserver) server).getOwner().equals(player.getUniqueId())) ? unlock : restrict
|
||||
);
|
||||
}
|
||||
|
||||
private byte[] generatePacket(boolean restrict) {
|
||||
ByteArrayOutputStream stream = new ByteArrayOutputStream();
|
||||
try {
|
||||
for(String restriction : Arrays.asList("no_xray", "no_noclip", "only_first_person", "only_recording_player")) {
|
||||
byte[] bytes = restriction.getBytes();
|
||||
stream.write(bytes.length);
|
||||
stream.write(bytes);
|
||||
stream.write(restrict ? 1 : 0);
|
||||
stream.write(1); // restrict
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new SecurityException(e);
|
||||
}
|
||||
return stream.toByteArray();
|
||||
restrict = stream.toByteArray();
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(ServerSwitchEvent event) {
|
||||
ProxiedPlayer player = event.getPlayer();
|
||||
ServerInfo server = player.getServer().getInfo();
|
||||
if(ProxyServer.getInstance().getServerInfo(BungeeCore.LOBBY_SERVER) == server)
|
||||
return;
|
||||
|
||||
Subserver subserver = Subserver.getSubserver(server);
|
||||
if(subserver instanceof Builderserver || (subserver instanceof Bauserver && ((Bauserver) subserver).getOwner().equals(player.getUniqueId())))
|
||||
return;
|
||||
|
||||
PluginMessage.send(player, "Replay|Restrict", "replaymod:restrict", restrict);
|
||||
}
|
||||
}
|
||||
|
@ -21,31 +21,17 @@ package de.steamwar.bungeecore.mods;
|
||||
|
||||
import com.google.gson.*;
|
||||
import de.steamwar.bungeecore.listeners.BasicListener;
|
||||
import lombok.AllArgsConstructor;
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
import net.md_5.bungee.api.ServerPing;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.ProxyPingEvent;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
import net.md_5.bungee.protocol.ProtocolConstants;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ServerListPing extends BasicListener implements JsonSerializer<ServerPing>, JsonDeserializer<ServerPing> {
|
||||
// https://github.com/Aizistral-Studios/No-Chat-Reports/discussions/206
|
||||
// https://github.com/Aizistral-Studios/No-Chat-Reports/wiki/How-to-Get-Safe-Server-Status
|
||||
// https://wiki.vg/Minecraft_Forge_Handshake#Changes_to_Server_List_Ping_2
|
||||
|
||||
private final Gson gson;
|
||||
private final Map<Integer, String> versionMap = new HashMap<>();
|
||||
private final FML2Channel[] fml2Channels = new FML2Channel[]{new FML2Channel("minecraft:register"), new FML2Channel("minecraft:unregister"), new FML2Channel("fml:handshake")};
|
||||
|
||||
private final Map<ServerPing, PendingConnection> connections = new ConcurrentHashMap<>();
|
||||
|
||||
public ServerListPing() {
|
||||
BungeeCord bungeeCord = BungeeCord.getInstance();
|
||||
@ -55,25 +41,11 @@ public class ServerListPing extends BasicListener implements JsonSerializer<Serv
|
||||
Field gsonField = BungeeCord.class.getDeclaredField("gson");
|
||||
gsonField.setAccessible(true);
|
||||
gsonField.set(bungeeCord, gson.newBuilder().registerTypeAdapter(ServerPing.class, this).create());
|
||||
|
||||
for(Field version : ProtocolConstants.class.getFields()) {
|
||||
String name = version.getName();
|
||||
if(version.getType() != int.class || !name.startsWith("MINECRAFT_"))
|
||||
continue;
|
||||
|
||||
versionMap.put(version.getInt(null), name.substring(10).replace('_', '.'));
|
||||
}
|
||||
} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
|
||||
throw new SecurityException("Failed to inject ServerListPing", e);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.HIGHEST)
|
||||
public void onPing(ProxyPingEvent event) {
|
||||
connections.put(event.getResponse(), event.getConnection());
|
||||
connections.values().removeIf(connection -> !connection.isConnected()); // Prevent long term leaks
|
||||
}
|
||||
|
||||
@Override
|
||||
public JsonElement serialize(ServerPing ping, Type type, JsonSerializationContext context) {
|
||||
JsonElement element = gson.toJsonTree(ping, type);
|
||||
@ -81,14 +53,6 @@ public class ServerListPing extends BasicListener implements JsonSerializer<Serv
|
||||
JsonObject object = element.getAsJsonObject();
|
||||
object.addProperty("preventsChatReports", true);
|
||||
|
||||
PendingConnection connection = connections.remove(ping);
|
||||
if(FML2.isFML2(connection)) {
|
||||
object.add("forgeData", gson.toJsonTree(new FML2Data(fml2Channels, new FML2Mod[]{
|
||||
new FML2Mod("forge", "ANY"),
|
||||
new FML2Mod("minecraft", versionMap.get(connection.getVersion()))
|
||||
}), FML2Data.class));
|
||||
}
|
||||
|
||||
return element;
|
||||
}
|
||||
|
||||
@ -96,24 +60,4 @@ public class ServerListPing extends BasicListener implements JsonSerializer<Serv
|
||||
public ServerPing deserialize(JsonElement element, Type type, JsonDeserializationContext context) throws JsonParseException {
|
||||
return gson.fromJson(element, ServerPing.class);
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class FML2Data {
|
||||
private final int fmlNetworkVersion = 2;
|
||||
private final FML2Channel[] channels;
|
||||
private final FML2Mod[] mods;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class FML2Channel {
|
||||
private final String res;
|
||||
private final String version = "FML2";
|
||||
private final boolean required = true;
|
||||
}
|
||||
|
||||
@AllArgsConstructor
|
||||
private static class FML2Mod {
|
||||
private final String modId;
|
||||
private final String modmarker;
|
||||
}
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ import java.io.DataOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class WorldDownloader {
|
||||
// https://wiki.vg/Plugin_channels/World_downloader
|
||||
// https://github.com/Pokechu22/WorldDownloader-Serverside-Companion
|
||||
// https://github.com/Pokechu22/WorldDownloader
|
||||
|
||||
private final byte[] controlPacket;
|
||||
|
||||
|
In neuem Issue referenzieren
Einen Benutzer sperren