From 2026893d950440e24aecbca4f47b23fce0d54535 Mon Sep 17 00:00:00 2001 From: Lixfel Date: Sat, 29 Jan 2022 13:16:30 +0100 Subject: [PATCH] Forge packet parsing Signed-off-by: Lixfel --- src/de/steamwar/bungeecore/BungeeCore.java | 1 + src/de/steamwar/bungeecore/Message.java | 24 +-- .../listeners/mods/CustomPacketHandler.java | 49 ------ .../bungeecore/listeners/mods/FMLPing.java | 2 +- .../bungeecore/listeners/mods/Forge.java | 151 ++++++++---------- .../bungeecore/listeners/mods/Forge12.java | 113 +++++++++++++ .../bungeecore/listeners/mods/Utils.java | 25 ++- src/de/steamwar/bungeecore/sql/UserGroup.java | 22 ++- 8 files changed, 224 insertions(+), 163 deletions(-) delete mode 100644 src/de/steamwar/bungeecore/listeners/mods/CustomPacketHandler.java create mode 100644 src/de/steamwar/bungeecore/listeners/mods/Forge12.java diff --git a/src/de/steamwar/bungeecore/BungeeCore.java b/src/de/steamwar/bungeecore/BungeeCore.java index 43a6a37..27b28dd 100644 --- a/src/de/steamwar/bungeecore/BungeeCore.java +++ b/src/de/steamwar/bungeecore/BungeeCore.java @@ -78,6 +78,7 @@ public class BungeeCore extends Plugin { errorLogger = new ErrorLogger(); new ConnectionListener(); new Forge(); + new Forge12(); new LabyMod(); new Badlion(); new ChatListener(); diff --git a/src/de/steamwar/bungeecore/Message.java b/src/de/steamwar/bungeecore/Message.java index b9a6f58..f755cdf 100644 --- a/src/de/steamwar/bungeecore/Message.java +++ b/src/de/steamwar/bungeecore/Message.java @@ -44,24 +44,26 @@ public class Message { }; public static TextComponent parseToComponent(String message, boolean prefixed, CommandSender sender, Object... params){ - return new TextComponent(TextComponent.fromLegacyText(parse(message, prefixed, sender, params))); + return new TextComponent(TextComponent.fromLegacyText(parse(message, prefixed, locale(sender), params))); } public static String parsePrefixed(String message, CommandSender sender, Object... params){ - return parse(message, true, sender, params); + return parse(message, true, locale(sender), params); } public static String parse(String message, CommandSender sender, Object... params){ - return parse(message, false, sender, params); + return parse(message, false, locale(sender), params); } - private static String parse(String message, boolean prefixed, CommandSender sender, Object... params){ - Locale locale = null; - if(sender instanceof ProxiedPlayer) - locale = ((ProxiedPlayer)sender).getLocale(); - if(locale == null) - locale = Locale.getDefault(); + public static String parse(String message, Locale locale, Object... params){ + return parse(message, false, locale, params); + } + private static Locale locale(CommandSender sender) { + return sender instanceof ProxiedPlayer ? ((ProxiedPlayer)sender).getLocale() : Locale.getDefault(); + } + + private static String parse(String message, boolean prefixed, Locale locale, Object... params){ ResourceBundle resourceBundle = ResourceBundle.getBundle("de.steamwar.messages.BungeeCore", locale, CONTROL); String pattern = ""; if(prefixed) @@ -72,7 +74,7 @@ public class Message { for (int i = 0; i < params.length; i++) { if(params[i] instanceof Message) { Message msg = (Message) params[i]; - params[i] = parse(msg.getMessage(), sender, msg.getParams()); + params[i] = parse(msg.getMessage(), false, locale, msg.getParams()); } else if(params[i] instanceof Date) { params[i] = DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale).format((Date) params[i]); } @@ -120,7 +122,7 @@ public class Message { public static void broadcast(String message, String onHover, ClickEvent onClick, Object... params){ for(ProxiedPlayer player : ProxyServer.getInstance().getPlayers()) - send(message, player, parse(onHover, false, player, params), onClick, params); + send(message, player, parse(onHover, player, params), onClick, params); } public static void broadcast(String message, Object... params){ diff --git a/src/de/steamwar/bungeecore/listeners/mods/CustomPacketHandler.java b/src/de/steamwar/bungeecore/listeners/mods/CustomPacketHandler.java deleted file mode 100644 index 949823e..0000000 --- a/src/de/steamwar/bungeecore/listeners/mods/CustomPacketHandler.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - This file is a part of the SteamWar software. - - Copyright (C) 2020 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 . -*/ -package de.steamwar.bungeecore.listeners.mods; - -import de.steamwar.bungeecore.BungeeCore; -import net.md_5.bungee.api.event.LoginEvent; -import net.md_5.bungee.netty.PacketHandler; -import net.md_5.bungee.protocol.packet.LoginPayloadResponse; - -public class CustomPacketHandler extends PacketHandler { - - private LoginEvent event; - - public CustomPacketHandler(LoginEvent event) { - this.event = event; - } - - @Override - public String toString() { - return null; - } - - public void handle(LoginPayloadResponse response){ - if(response.getData() == null) { - event.completeIntent(BungeeCore.get()); - return; - } - - byte[] data = response.getData(); - - event.completeIntent(BungeeCore.get()); - } -} diff --git a/src/de/steamwar/bungeecore/listeners/mods/FMLPing.java b/src/de/steamwar/bungeecore/listeners/mods/FMLPing.java index 5ce9aaf..4ed9a01 100644 --- a/src/de/steamwar/bungeecore/listeners/mods/FMLPing.java +++ b/src/de/steamwar/bungeecore/listeners/mods/FMLPing.java @@ -25,7 +25,7 @@ import java.util.List; public class FMLPing extends ServerPing { - private ForgeData forgeData = new ForgeData(); + private final ForgeData forgeData = new ForgeData(); public FMLPing(ServerPing existing) { super(existing.getVersion(), existing.getPlayers(), existing.getDescriptionComponent(), existing.getFaviconObject()); diff --git a/src/de/steamwar/bungeecore/listeners/mods/Forge.java b/src/de/steamwar/bungeecore/listeners/mods/Forge.java index 64c1ce0..99d4614 100644 --- a/src/de/steamwar/bungeecore/listeners/mods/Forge.java +++ b/src/de/steamwar/bungeecore/listeners/mods/Forge.java @@ -19,67 +19,38 @@ package de.steamwar.bungeecore.listeners.mods; -import com.google.common.collect.ImmutableMap; import de.steamwar.bungeecore.BungeeCore; import de.steamwar.bungeecore.listeners.BasicListener; import de.steamwar.bungeecore.sql.Mod; -import io.netty.buffer.ByteBuf; -import io.netty.buffer.UnpooledByteBufAllocator; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.api.connection.Connection; -import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.event.LoginEvent; -import net.md_5.bungee.api.event.PluginMessageEvent; -import net.md_5.bungee.api.event.PostLoginEvent; import net.md_5.bungee.api.event.ProxyPingEvent; 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.packet.LoginPayloadRequest; +import net.md_5.bungee.protocol.packet.LoginPayloadResponse; import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.util.*; -import java.util.concurrent.TimeUnit; +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; import java.util.logging.Level; public class Forge extends BasicListener { - private static final String FMLHS = "FML|HS"; private static final String WRAPPER = "fml:loginwrapper"; - private static final byte[] REGISTER; - private static final byte[] HELLO = new byte[]{0, 2, 0, 0, 0, 0}; - private static final Set unlocked = new HashSet<>(); + private static final Field initialHandlerCh; static{ - ByteBuf buf = UnpooledByteBufAllocator.DEFAULT.directBuffer(7); - buf.writeByte(6); - buf.writeCharSequence(FMLHS, StandardCharsets.UTF_8); - REGISTER = new byte[buf.readableBytes()]; - buf.readBytes(REGISTER); - } - - @EventHandler - public void onServerConnected(LoginEvent event){ - if(event.getConnection().getVersion() < 340) return; //1.13+ - event.getConnection().unsafe().sendPacket(new LoginPayloadRequest(1,WRAPPER, new byte[]{13,102,109,108,58,104,97,110,100,115,104,97,107,101,4,1,0,0,0})); - - InitialHandler handler = (InitialHandler) event.getConnection(); - - Field ch; - ChannelWrapper wrapper; - - try{ - ch = handler.getClass().getDeclaredField("ch"); - ch.setAccessible(true); - wrapper = (ChannelWrapper) ch.get(handler); - } catch (NoSuchFieldException | IllegalAccessException e) { - BungeeCore.get().getLogger().log(Level.SEVERE, "Could not get Channel", e); - return; + try { + initialHandlerCh = InitialHandler.class.getDeclaredField("ch"); + } catch (NoSuchFieldException e) { + throw new SecurityException("Could not initialize Reflection", e); } - event.registerIntent(BungeeCore.get()); - wrapper.getHandle().pipeline().get(HandlerBoss.class).setHandler(new CustomPacketHandler(event)); + initialHandlerCh.setAccessible(true); } @EventHandler @@ -88,62 +59,70 @@ public class Forge extends BasicListener { } @EventHandler - public void onPostLogin(PostLoginEvent event) { - ProxiedPlayer player = event.getPlayer(); + public void onServerConnected(LoginEvent event){ + if(event.getConnection().getVersion() < 340) return; //1.13+ - synchronized (unlocked) { - if(unlocked.contains(player.getUniqueId())){ - unlocked.remove(player.getUniqueId()); - return; - } + //fml:handshake without mods, channels and registries + //for more information see https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29 + event.getConnection().unsafe().sendPacket(new LoginPayloadRequest(1, WRAPPER, new byte[]{13,102,109,108,58,104,97,110,100,115,104,97,107,101,4,1,0,0,0})); + + InitialHandler handler = (InitialHandler) event.getConnection(); + + ChannelWrapper wrapper; + try{ + wrapper = (ChannelWrapper) initialHandlerCh.get(handler); + } catch (IllegalAccessException e) { + BungeeCore.get().getLogger().log(Level.SEVERE, "Could not get Channel", e); + return; } - if(player.getPendingConnection().getVersion() <= 340) { - player.sendData("REGISTER", REGISTER); //1.12- - player.sendData(FMLHS, Forge.HELLO); - } + event.registerIntent(BungeeCore.get()); + wrapper.getHandle().pipeline().get(HandlerBoss.class).setHandler(new CustomPacketHandler(event)); } - @EventHandler - public void onPluginMessageEvent(PluginMessageEvent e){ - if(!e.getTag().equals(FMLHS)) - return; + private static class CustomPacketHandler extends PacketHandler { + private final LoginEvent event; - e.setCancelled(true); - byte[] data = e.getData(); + public CustomPacketHandler(LoginEvent event) { + this.event = event; + } - Connection sender = e.getSender(); - if(!(sender instanceof ProxiedPlayer)) - return; - ProxiedPlayer p = (ProxiedPlayer) sender; + @Override + public String toString() { + return "SteamWar Forge Handler"; + } - if (data[0] == 2) { - Utils.VarInt numMods = Utils.readVarInt(data, 1); - List mods = new LinkedList<>(); - - int bytePos = 1 + numMods.length; - for (int i = 0; i < numMods.value; i++) { - byte[] name = Arrays.copyOfRange(data, bytePos + 1, bytePos + data[bytePos] + 1); - bytePos += 1 + data[bytePos]; - //Version information is unused - bytePos += 1 + data[bytePos]; - - mods.add(Mod.get(new String(name), Mod.Platform.FORGE)); + @Override + public void handle(LoginPayloadResponse response){ + byte[] data = response.getData(); + if(data == null) { + event.completeIntent(BungeeCore.get()); + return; } - if (Utils.handleMods(p, mods)) { - synchronized (unlocked) { - unlocked.add(p.getUniqueId()); - } - ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), - () -> p.disconnect(BungeeCore.stringToText("§7Deine installierten Mods wurden überprüft\n§aDu kannst nun §eSteam§8War §abetreten")), - 2, TimeUnit.SECONDS); - ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), () -> { - synchronized (unlocked) { - unlocked.remove(p.getUniqueId()); - } - }, 30, TimeUnit.SECONDS); + //for more information see https://wiki.vg/Minecraft_Forge_Handshake#FML2_protocol_.281.13_-_Current.29 + Utils.VarInt length = Utils.readVarInt(data, 0); + assert length.length + length.value == data.length; + + Utils.VarInt packetId = Utils.readVarInt(data, length.length); + assert packetId.value == 2; + + int pos = length.length + packetId.length; + Utils.VarInt modCount = Utils.readVarInt(data, pos); + pos += modCount.length; + + List mods = new ArrayList<>(); + for(int i = 0; i < modCount.value; i++) { + Utils.VarInt nameLength = Utils.readVarInt(data, pos); + pos += nameLength.length; + + mods.add(Mod.get(new String(data, pos, nameLength.value), Mod.Platform.FORGE)); + pos += nameLength.value; } + + PendingConnection connection = event.getConnection(); + if(Utils.handleMods(connection.getUniqueId(), Locale.getDefault(), connection::disconnect, mods)) + event.completeIntent(BungeeCore.get()); } } } diff --git a/src/de/steamwar/bungeecore/listeners/mods/Forge12.java b/src/de/steamwar/bungeecore/listeners/mods/Forge12.java new file mode 100644 index 0000000..eb463f4 --- /dev/null +++ b/src/de/steamwar/bungeecore/listeners/mods/Forge12.java @@ -0,0 +1,113 @@ +/* + This file is a part of the SteamWar software. + + Copyright (C) 2022 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 . +*/ + +package de.steamwar.bungeecore.listeners.mods; + +import de.steamwar.bungeecore.BungeeCore; +import de.steamwar.bungeecore.listeners.BasicListener; +import de.steamwar.bungeecore.sql.Mod; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.UnpooledByteBufAllocator; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.Connection; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PluginMessageEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.event.EventHandler; + +import java.nio.charset.StandardCharsets; +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class Forge12 extends BasicListener { + private static final String FMLHS = "FML|HS"; + private static final byte[] REGISTER; + private static final byte[] HELLO = new byte[]{0, 2, 0, 0, 0, 0}; + + private static final Set unlocked = new HashSet<>(); + + static { + ByteBuf buf = UnpooledByteBufAllocator.DEFAULT.directBuffer(7); + buf.writeByte(6); + buf.writeCharSequence(FMLHS, StandardCharsets.UTF_8); + REGISTER = new byte[buf.readableBytes()]; + buf.readBytes(REGISTER); + } + + + @EventHandler + public void onPostLogin(PostLoginEvent event) { + ProxiedPlayer player = event.getPlayer(); + + synchronized (unlocked) { + if(unlocked.contains(player.getUniqueId())){ + unlocked.remove(player.getUniqueId()); + return; + } + } + + if(player.getPendingConnection().getVersion() <= 340) { + player.sendData("REGISTER", REGISTER); //1.12- + player.sendData(FMLHS, HELLO); + } + } + + @EventHandler + public void onPluginMessageEvent(PluginMessageEvent e){ + if(!e.getTag().equals(FMLHS)) + return; + + e.setCancelled(true); + byte[] data = e.getData(); + + Connection sender = e.getSender(); + if(!(sender instanceof ProxiedPlayer)) + return; + ProxiedPlayer p = (ProxiedPlayer) sender; + + if (data[0] == 2) { + Utils.VarInt numMods = Utils.readVarInt(data, 1); + List mods = new LinkedList<>(); + + int bytePos = 1 + numMods.length; + for (int i = 0; i < numMods.value; i++) { + byte[] name = Arrays.copyOfRange(data, bytePos + 1, bytePos + data[bytePos] + 1); + bytePos += 1 + data[bytePos]; + //Version information is unused + bytePos += 1 + data[bytePos]; + + mods.add(Mod.get(new String(name), Mod.Platform.FORGE)); + } + + if (Utils.handleMods(p, mods)) { + synchronized (unlocked) { + unlocked.add(p.getUniqueId()); + } + ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), + () -> p.disconnect(BungeeCore.stringToText("§7Deine installierten Mods wurden überprüft\n§aDu kannst nun §eSteam§8War §abetreten")), + 2, TimeUnit.SECONDS); + ProxyServer.getInstance().getScheduler().schedule(BungeeCore.get(), () -> { + synchronized (unlocked) { + unlocked.remove(p.getUniqueId()); + } + }, 30, TimeUnit.SECONDS); + } + } + } +} diff --git a/src/de/steamwar/bungeecore/listeners/mods/Utils.java b/src/de/steamwar/bungeecore/listeners/mods/Utils.java index 9ed160a..417f846 100644 --- a/src/de/steamwar/bungeecore/listeners/mods/Utils.java +++ b/src/de/steamwar/bungeecore/listeners/mods/Utils.java @@ -25,6 +25,8 @@ import de.steamwar.bungeecore.sql.Mod; import de.steamwar.bungeecore.sql.Mod.ModType; import de.steamwar.bungeecore.sql.Punishment; import de.steamwar.bungeecore.sql.SteamwarUser; +import net.md_5.bungee.api.chat.BaseComponent; +import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.connection.ProxiedPlayer; import java.sql.Timestamp; @@ -32,6 +34,9 @@ import java.time.Instant; import java.time.temporal.ChronoUnit; import java.util.Iterator; import java.util.List; +import java.util.Locale; +import java.util.UUID; +import java.util.function.Consumer; import java.util.logging.Level; class Utils { @@ -55,9 +60,13 @@ class Utils { return new VarInt(numRead, result); } - static boolean handleMods(ProxiedPlayer player, List mods){ - SteamwarUser user = SteamwarUser.get(player.getUniqueId()); - boolean privileged = player.hasPermission("bungeecore.youtubermods"); + static boolean handleMods(ProxiedPlayer player, List mods) { + return handleMods(player.getUniqueId(), player.getLocale(), player::disconnect, mods); + } + + static boolean handleMods(UUID uuid, Locale locale, Consumer disconnect, List mods){ + SteamwarUser user = SteamwarUser.get(uuid); + boolean privileged = user.getUserGroup().privilegedMods(); ModType max = ModType.YELLOW; Iterator it = mods.iterator(); @@ -74,9 +83,9 @@ class Utils { if(mods.size() == 1){ if(max == ModType.YELLOW) - player.disconnect(BungeeCore.stringToText(Message.parse("MOD_YELLOW_SING", player, mods.get(0).getModName()))); + disconnect.accept(TextComponent.fromLegacyText(Message.parse("MOD_YELLOW_SING", locale, mods.get(0).getModName()))); else{ - user.punish(Punishment.PunishmentType.Ban, Timestamp.from(Instant.now().plus(7, ChronoUnit.DAYS)), Message.parse("MOD_RED_SING", player, mods.get(0).getModName()), 0, false); + user.punish(Punishment.PunishmentType.Ban, Timestamp.from(Instant.now().plus(7, ChronoUnit.DAYS)), Message.parse("MOD_RED_SING", locale, mods.get(0).getModName()), 0, false); BungeeCore.log(Level.SEVERE, user.getUserName() + " " + user.getId() + " wurde automatisch wegen des Mods " + mods.get(0).getModName() + " gebannt."); } }else{ @@ -84,10 +93,10 @@ class Utils { mods.forEach(mod -> sb.append(mod.getModName()).append('\n')); if(max == ModType.YELLOW) - player.disconnect(BungeeCore.stringToText(Message.parse("MOD_YELLOW_PLUR", player, sb.toString()))); + disconnect.accept(TextComponent.fromLegacyText(Message.parse("MOD_YELLOW_PLUR", locale, sb.toString()))); else{ - user.punish(Punishment.PunishmentType.Ban, Timestamp.from(Instant.now().plus(7, ChronoUnit.DAYS)), Message.parse("MOD_RED_PLUR", player, sb.toString()), 0, false); - BungeeCore.log(Level.SEVERE, user.getUserName() + " " + user.getId() + " wurde automatisch wegen der Mods " + sb.toString() + " gebannt."); + user.punish(Punishment.PunishmentType.Ban, Timestamp.from(Instant.now().plus(7, ChronoUnit.DAYS)), Message.parse("MOD_RED_PLUR", locale, sb.toString()), 0, false); + BungeeCore.log(Level.SEVERE, user.getUserName() + " " + user.getId() + " wurde automatisch wegen der Mods " + sb + " gebannt."); } } diff --git a/src/de/steamwar/bungeecore/sql/UserGroup.java b/src/de/steamwar/bungeecore/sql/UserGroup.java index 73273dc..e6c30f9 100644 --- a/src/de/steamwar/bungeecore/sql/UserGroup.java +++ b/src/de/steamwar/bungeecore/sql/UserGroup.java @@ -24,26 +24,28 @@ import java.util.stream.Collectors; public enum UserGroup { - Admin("§4", "§e", true, true, true), - Developer("§3", "§f", true, true, true), - Moderator("§c", "§f", true, true, true), - Supporter("§9", "§f", false, true, true), - Builder("§2", "§f", false, true, false), - YouTuber("§5", "§f", false, false, false), - Member("§7", "§7", false, false, false); + Admin("§4", "§e", true, true, true, true), + Developer("§3", "§f", true, true, true, true), + Moderator("§c", "§f", true, true, true, true), + Supporter("§9", "§f", false, true, true, true), + Builder("§2", "§f", false, true, false, true), + YouTuber("§5", "§f", false, false, false, true), + Member("§7", "§7", false, false, false, false); private final String colorCode; private final String chatColorCode; private final boolean adminGroup; private final boolean teamGroup; private final boolean checkSchematics; + private final boolean privilegedMods; - UserGroup(String colorCode, String chatColorCode, boolean adminGroup, boolean teamGroup, boolean checkSchematics) { + UserGroup(String colorCode, String chatColorCode, boolean adminGroup, boolean teamGroup, boolean checkSchematics, boolean privilegedMods) { this.colorCode = colorCode; this.chatColorCode = chatColorCode; this.adminGroup = adminGroup; this.teamGroup = teamGroup; this.checkSchematics = checkSchematics; + this.privilegedMods = privilegedMods; } public String getColorCode() { @@ -62,6 +64,10 @@ public enum UserGroup { return checkSchematics; } + public boolean privilegedMods() { + return privilegedMods; + } + public String getChatColorCode() { return chatColorCode; }