From 97d42528bc5c058f832e26a13b6b51a5d7252634 Mon Sep 17 00:00:00 2001 From: ThebestkillerTBK <2593828650@qq.com> Date: Mon, 10 Oct 2022 22:33:05 +0800 Subject: [PATCH] Item Generator and Server finder --- README.md | 2 + .../anticope/rejects/MeteorRejectsAddon.java | 3 +- .../rejects/gui/servers/CleanUpScreen.java | 124 ++++++++ .../gui/servers/LegacyServerFinderScreen.java | 201 ++++++++++++ .../gui/servers/ServerFinderScreen.java | 299 ++++++++++++++++++ .../gui/servers/ServerManagerScreen.java | 161 ++++++++++ .../mixin/MultiplayerScreenAccessor.java | 12 + .../rejects/mixin/MultiplayerScreenMixin.java | 26 ++ .../rejects/mixin/ServerListAccessor.java | 14 + .../anticope/rejects/modules/ExtraElytra.java | 7 - .../rejects/modules/ItemGenerator.java | 68 ++++ .../rejects/utils/server/IPAddress.java | 92 ++++++ .../IServerFinderDisconnectListener.java | 7 + .../server/IServerFinderDoneListener.java | 7 + .../utils/server/LegacyServerPinger.java | 60 ++++ .../rejects/utils/server/MServerInfo.java | 37 +++ .../utils/server/ServerListPinger.java | 290 +++++++++++++++++ .../rejects/utils/server/ServerPinger.java | 213 +++++++++++++ src/main/resources/meteor-rejects.mixins.json | 5 +- 19 files changed, 1619 insertions(+), 9 deletions(-) create mode 100644 src/main/java/anticope/rejects/gui/servers/CleanUpScreen.java create mode 100644 src/main/java/anticope/rejects/gui/servers/LegacyServerFinderScreen.java create mode 100644 src/main/java/anticope/rejects/gui/servers/ServerFinderScreen.java create mode 100644 src/main/java/anticope/rejects/gui/servers/ServerManagerScreen.java create mode 100644 src/main/java/anticope/rejects/mixin/MultiplayerScreenAccessor.java create mode 100644 src/main/java/anticope/rejects/mixin/MultiplayerScreenMixin.java create mode 100644 src/main/java/anticope/rejects/mixin/ServerListAccessor.java create mode 100644 src/main/java/anticope/rejects/modules/ItemGenerator.java create mode 100644 src/main/java/anticope/rejects/utils/server/IPAddress.java create mode 100644 src/main/java/anticope/rejects/utils/server/IServerFinderDisconnectListener.java create mode 100644 src/main/java/anticope/rejects/utils/server/IServerFinderDoneListener.java create mode 100644 src/main/java/anticope/rejects/utils/server/LegacyServerPinger.java create mode 100644 src/main/java/anticope/rejects/utils/server/MServerInfo.java create mode 100644 src/main/java/anticope/rejects/utils/server/ServerListPinger.java create mode 100644 src/main/java/anticope/rejects/utils/server/ServerPinger.java diff --git a/README.md b/README.md index d58a2c4..665dd0b 100644 --- a/README.md +++ b/README.md @@ -55,6 +55,7 @@ - Ghost Mode (Taken from an [unmerged PR](https://github.com/MeteorDevelopment/meteor-client/pull/1932)) - Glide (Ported from [Wurst](https://github.com/Wurst-Imperium/Wurst7/tree)) - Insta Mine (Removed from Meteor in [62cd0](https://github.com/MeteorDevelopment/meteor-client/commit/62cd0461e48a6c50f040bf48de25be1fa4eba77e)) +- Item generator (Ported from [Wurst](https://github.com/Wurst-Imperium/Wurst7/tree)) - InteractionMenu (Ported from [BleachHack](https://github.com/BleachDrinker420/BleachHack/pull/211)) - Lavacast - NewChunks (Ported from [BleackHack](https://github.com/BleachDrinker420/BleachHack/blob/master/BleachHack-Fabric-1.17/src/main/java/bleach/hack/module/mods/NewChunks.java)) @@ -65,6 +66,7 @@ - Rendering - SkeletonESP (Ported from [JexClient](https://github.com/DustinRepo/JexClient-main/blob/main/src/main/java/me/dustin/jex/feature/mod/impl/render/Skeletons.java)) - SoundLocator +- Server Finder (Ported from [MeteorAdditions](https://github.com/JFronny/MeteorAdditions)) - TreeAura (Taken from an [unmerged PR](https://github.com/MeteorDevelopment/meteor-client/pull/2138)) ### Modifications diff --git a/src/main/java/anticope/rejects/MeteorRejectsAddon.java b/src/main/java/anticope/rejects/MeteorRejectsAddon.java index a095fbe..cdd43e4 100644 --- a/src/main/java/anticope/rejects/MeteorRejectsAddon.java +++ b/src/main/java/anticope/rejects/MeteorRejectsAddon.java @@ -57,6 +57,7 @@ public class MeteorRejectsAddon extends MeteorAddon { modules.add(new GhostMode()); modules.add(new Glide()); modules.add(new InstaMine()); + modules.add(new ItemGenerator()); modules.add(new InteractionMenu()); modules.add(new Lavacast()); modules.add(new NewChunks()); @@ -121,7 +122,7 @@ public class MeteorRejectsAddon extends MeteorAddon { .get().getMetadata() .getCustomValue("github:sha") .getAsString(); - LOG.info(String.format("Rejects version: %s", commit.toString())); + LOG.info(String.format("Rejects version: %s", commit)); return commit.isEmpty() ? null : commit.trim(); } diff --git a/src/main/java/anticope/rejects/gui/servers/CleanUpScreen.java b/src/main/java/anticope/rejects/gui/servers/CleanUpScreen.java new file mode 100644 index 0000000..bd3b55b --- /dev/null +++ b/src/main/java/anticope/rejects/gui/servers/CleanUpScreen.java @@ -0,0 +1,124 @@ +package anticope.rejects.gui.servers; + +import anticope.rejects.mixin.MultiplayerScreenAccessor; +import anticope.rejects.mixin.ServerListAccessor; +import meteordevelopment.meteorclient.gui.GuiTheme; +import meteordevelopment.meteorclient.gui.WindowScreen; +import meteordevelopment.meteorclient.gui.widgets.containers.WTable; +import meteordevelopment.meteorclient.gui.widgets.pressable.WCheckbox; +import meteordevelopment.meteorclient.utils.render.color.Color; +import net.minecraft.SharedConstants; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget; +import net.minecraft.client.network.ServerInfo; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class CleanUpScreen extends WindowScreen { + private final MultiplayerScreen multiplayerScreen; + private final WCheckbox removeAll; + private final WCheckbox removeFailed; + private final WCheckbox removeOutdated; + private final WCheckbox removeUnknown; + private final WCheckbox removeGriefMe; + private final WCheckbox removeDuplicates; + private final WCheckbox rename; + + public CleanUpScreen(GuiTheme theme, MultiplayerScreen multiplayerScreen, Screen parent) { + super(theme, "Clean Up"); + this.multiplayerScreen = multiplayerScreen; + this.parent = parent; + removeUnknown = theme.checkbox(true); + removeOutdated = theme.checkbox(false); + removeFailed = theme.checkbox(true); + removeGriefMe = theme.checkbox(false); + removeAll = theme.checkbox(false); + removeDuplicates = theme.checkbox(true); + rename = theme.checkbox(true); + } + + @Override + public void initWidgets() { + WTable table = add(new WTable()).widget(); + table.add(theme.label("Remove:")); + table.row(); + table.add(theme.label("Unknown Hosts:")).widget().tooltip = ""; + table.add(removeUnknown).widget(); + table.row(); + table.add(theme.label("Outdated Servers:")); + table.add(removeOutdated).widget(); + table.row(); + table.add(theme.label("Failed Ping:")); + table.add(removeFailed).widget(); + table.row(); + table.add(theme.label("\"Server discovery\" Servers:")); + table.add(removeGriefMe).widget(); + table.row(); + table.add(theme.label("Everything:")).widget().color = new Color(255, 0, 0); + table.add(removeAll).widget(); + table.row(); + table.add(theme.label("Duplicates:")); + table.add(removeDuplicates).widget(); + table.row(); + table.add(theme.label("Rename all Servers:")); + table.add(rename).widget(); + table.row(); + table.add(theme.button("Execute!")).expandX().widget().action = this::cleanUp; + } + + private void cleanUp() { + Set knownIPs = new HashSet<>(); + List servers = ((ServerListAccessor) multiplayerScreen.getServerList()).getServers(); + for (ServerInfo server : servers.toArray(ServerInfo[]::new)) { + if (removeAll.checked || shouldRemove(server, knownIPs)) + servers.remove(server); + } + + if (rename.checked) + for (int i = 0; i < servers.size(); i++) { + ServerInfo server = servers.get(i); + server.name = "Server discovery " + (i + 1); + } + + saveServerList(); + client.setScreen(parent); + } + + private boolean shouldRemove(ServerInfo server, Set knownIPs) { + return server != null && (removeUnknown.checked && isUnknownHost(server) + || removeOutdated.checked && !isSameProtocol(server) + || removeFailed.checked && isFailedPing(server) + || removeGriefMe.checked && isGriefMeServer(server) + || removeDuplicates.checked && !knownIPs.add(server.address)); + } + + private boolean isUnknownHost(ServerInfo server) { + if (server.label == null || server.label.getString() == null) return false; + + return server.label.getString().equals("\u00a74Can't resolve hostname"); + } + + private boolean isSameProtocol(ServerInfo server) { + return server.protocolVersion == SharedConstants.getGameVersion().getProtocolVersion(); + } + + private boolean isFailedPing(ServerInfo server) { + return server.ping != -2L && server.ping < 0L; + } + + private boolean isGriefMeServer(ServerInfo server) { + return server.name != null && server.name.startsWith("Server discovery "); + } + + private void saveServerList() { + multiplayerScreen.getServerList().saveFile(); + + MultiplayerServerListWidget serverListSelector = ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget(); + + serverListSelector.setSelected(null); + serverListSelector.setServers(multiplayerScreen.getServerList()); + } +} diff --git a/src/main/java/anticope/rejects/gui/servers/LegacyServerFinderScreen.java b/src/main/java/anticope/rejects/gui/servers/LegacyServerFinderScreen.java new file mode 100644 index 0000000..568fd2d --- /dev/null +++ b/src/main/java/anticope/rejects/gui/servers/LegacyServerFinderScreen.java @@ -0,0 +1,201 @@ +package anticope.rejects.gui.servers; + +import anticope.rejects.mixin.MultiplayerScreenAccessor; +import anticope.rejects.utils.server.LegacyServerPinger; +import meteordevelopment.meteorclient.gui.GuiTheme; +import meteordevelopment.meteorclient.gui.WindowScreen; +import meteordevelopment.meteorclient.gui.widgets.WLabel; +import meteordevelopment.meteorclient.gui.widgets.containers.WTable; +import meteordevelopment.meteorclient.gui.widgets.input.WIntEdit; +import meteordevelopment.meteorclient.gui.widgets.input.WTextBox; +import meteordevelopment.meteorclient.gui.widgets.pressable.WButton; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; + +public class LegacyServerFinderScreen extends WindowScreen { + private final MultiplayerScreen multiplayerScreen; + private final WTextBox ipBox; + private final WIntEdit maxThreadsBox; + private final WButton searchButton; + private final WLabel stateLabel; + private final WLabel checkedLabel; + private final WLabel workingLabel; + private ServerFinderState state; + private int maxThreads; + private int checked; + private int working; + + public LegacyServerFinderScreen(GuiTheme theme, MultiplayerScreen multiplayerScreen, Screen parent) { + super(theme, "Legacy Server Discovery"); + this.multiplayerScreen = multiplayerScreen; + this.parent = parent; + ipBox = theme.textBox("127.0.0.1"); + maxThreadsBox = theme.intEdit(128, 1, 256, 1, 256); + stateLabel = theme.label(""); + checkedLabel = theme.label(""); + workingLabel = theme.label(""); + searchButton = theme.button("Search"); + state = ServerFinderState.NOT_RUNNING; + } + + @Override + public void initWidgets() { + add(theme.label("This will search for servers with similar IPs")); + add(theme.label("to the IP you type into the field below.")); + add(theme.label("The servers it finds will be added to your server list.")); + WTable table = add(new WTable()).expandX().widget(); + table.add(theme.label("Server address:")); + table.add(ipBox).expandX(); + table.row(); + table.add(theme.label("Max. Threads:")); + table.add(maxThreadsBox); + add(stateLabel); + add(checkedLabel); + add(workingLabel); + add(searchButton).expandX(); + searchButton.action = this::searchOrCancel; + } + + private void searchOrCancel() { + if (state.isRunning()) { + state = ServerFinderState.CANCELLED; + return; + } + + state = ServerFinderState.RESOLVING; + maxThreads = maxThreadsBox.get(); + checked = 0; + working = 0; + + new Thread(this::findServers, "Server Discovery").start(); + } + + private void findServers() { + try { + InetAddress addr = + InetAddress.getByName(ipBox.get().split(":")[0].trim()); + + int[] ipParts = new int[4]; + for (int i = 0; i < 4; i++) + ipParts[i] = addr.getAddress()[i] & 0xff; + + state = ServerFinderState.SEARCHING; + ArrayList pingers = new ArrayList<>(); + int[] changes = {0, 1, -1, 2, -2, 3, -3}; + for (int change : changes) + for (int i2 = 0; i2 <= 255; i2++) { + if (state == ServerFinderState.CANCELLED) + return; + + int[] ipParts2 = ipParts.clone(); + ipParts2[2] = ipParts[2] + change & 0xff; + ipParts2[3] = i2; + String ip = ipParts2[0] + "." + ipParts2[1] + "." + + ipParts2[2] + "." + ipParts2[3]; + + LegacyServerPinger pinger = new LegacyServerPinger(); + pinger.ping(ip); + pingers.add(pinger); + while (pingers.size() >= maxThreads) { + if (state == ServerFinderState.CANCELLED) + return; + + updatePingers(pingers); + } + } + while (pingers.size() > 0) { + if (state == ServerFinderState.CANCELLED) + return; + + updatePingers(pingers); + } + state = ServerFinderState.DONE; + + } catch (UnknownHostException e) { + state = ServerFinderState.UNKNOWN_HOST; + + } catch (Exception e) { + e.printStackTrace(); + state = ServerFinderState.ERROR; + } + } + + @Override + public void tick() { + searchButton.set(state.isRunning() ? "Cancel" : "Search"); + if (state.isRunning()) { + ipBox.setFocused(false); + maxThreadsBox.set(maxThreads); + } + stateLabel.set(state.toString()); + checkedLabel.set("Checked: " + checked + " / 1792"); + workingLabel.set("Working: " + working); + searchButton.visible = !ipBox.get().isEmpty(); + } + + private boolean isServerInList(String ip) { + for (int i = 0; i < multiplayerScreen.getServerList().size(); i++) + if (multiplayerScreen.getServerList().get(i).address.equals(ip)) + return true; + + return false; + } + + private void updatePingers(ArrayList pingers) { + for (int i = 0; i < pingers.size(); i++) + if (!pingers.get(i).isStillPinging()) { + checked++; + if (pingers.get(i).isWorking()) { + working++; + + if (!isServerInList(pingers.get(i).getServerIP())) { + multiplayerScreen.getServerList() + .add(new ServerInfo("Server discovery " + working, + pingers.get(i).getServerIP(), false), false); + multiplayerScreen.getServerList().saveFile(); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget() + .setSelected(null); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget() + .setServers(multiplayerScreen.getServerList()); + } + } + pingers.remove(i); + } + } + + @Override + public void close() { + state = ServerFinderState.CANCELLED; + super.close(); + } + + enum ServerFinderState { + NOT_RUNNING(""), + SEARCHING("Searching..."), + RESOLVING("Resolving..."), + UNKNOWN_HOST("Unknown Host!"), + CANCELLED("Cancelled!"), + DONE("Done!"), + ERROR("An error occurred!"); + + private final String name; + + ServerFinderState(String name) { + this.name = name; + } + + public boolean isRunning() { + return this == SEARCHING || this == RESOLVING; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/src/main/java/anticope/rejects/gui/servers/ServerFinderScreen.java b/src/main/java/anticope/rejects/gui/servers/ServerFinderScreen.java new file mode 100644 index 0000000..e749c91 --- /dev/null +++ b/src/main/java/anticope/rejects/gui/servers/ServerFinderScreen.java @@ -0,0 +1,299 @@ +package anticope.rejects.gui.servers; + +import anticope.rejects.mixin.MultiplayerScreenAccessor; +import anticope.rejects.utils.server.IServerFinderDoneListener; +import anticope.rejects.utils.server.MServerInfo; +import anticope.rejects.utils.server.ServerPinger; +import meteordevelopment.meteorclient.gui.GuiTheme; +import meteordevelopment.meteorclient.gui.WindowScreen; +import meteordevelopment.meteorclient.gui.widgets.WLabel; +import meteordevelopment.meteorclient.gui.widgets.containers.WHorizontalList; +import meteordevelopment.meteorclient.gui.widgets.containers.WTable; +import meteordevelopment.meteorclient.gui.widgets.input.WIntEdit; +import meteordevelopment.meteorclient.gui.widgets.input.WTextBox; +import meteordevelopment.meteorclient.gui.widgets.pressable.WButton; +import meteordevelopment.meteorclient.gui.widgets.pressable.WCheckbox; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; + +import java.net.InetAddress; +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.Stack; + +public class ServerFinderScreen extends WindowScreen implements IServerFinderDoneListener { + public static ServerFinderScreen instance = null; + private static int searchNumber = 0; + private final MultiplayerScreen multiplayerScreen; + private final WTextBox ipBox; + private final WTextBox versionBox; + private final WIntEdit maxThreadsBox; + private final WButton searchButton; + private final WLabel stateLabel; + private final WLabel checkedLabel; + private final WLabel workingLabel; + private final WCheckbox scanPortsBox; + private final Stack ipsToPing = new Stack<>(); + private final Object serverFinderLock = new Object(); + private ServerFinderState state; + private int maxThreads; + private volatile int numActiveThreads; + private volatile int checked; + private volatile int working; + private int targetChecked = 1792; + private ArrayList versionFilters = new ArrayList<>(); + private int playerCountFilter = 0; + + public ServerFinderScreen(GuiTheme theme, MultiplayerScreen multiplayerScreen, Screen parent) { + super(theme, "Server Discovery"); + this.multiplayerScreen = multiplayerScreen; + this.parent = parent; + ipBox = theme.textBox("127.0.0.1"); + versionBox = theme.textBox("1.18; 1.17; 1.16; 1.15; 1.14; 1.13; 1.12; 1.11; 1.10; 1.9; 1.8"); + maxThreadsBox = theme.intEdit(128, 1, 256, 1, 256); + stateLabel = theme.label(""); + checkedLabel = theme.label(""); + searchButton = theme.button("Search"); + workingLabel = theme.label(""); + scanPortsBox = theme.checkbox(true); + state = ServerFinderState.NOT_RUNNING; + newSearch(); + instance = this; + } + + public static int getSearchNumber() { + return searchNumber; + } + + @Override + public void initWidgets() { + add(theme.label("This will search for servers with similar IPs")); + add(theme.label("to the IP you type into the field below.")); + add(theme.label("The servers it finds will be added to your server list.")); + WTable table = add(new WTable()).expandX().widget(); + table.add(theme.label("Server address:")); + table.add(ipBox).expandX(); + table.row(); + table.add(theme.label("Max. Threads:")); + table.add(maxThreadsBox); + table.row(); + table.add(theme.label("Scan ports")); + table.add(scanPortsBox); + table.row(); + table.add(theme.label("Versions:")); + table.add(versionBox).expandX(); + add(stateLabel); + add(checkedLabel); + add(workingLabel); + WHorizontalList list = add(theme.horizontalList()).expandX().widget(); + list.add(searchButton).expandX(); + searchButton.action = this::searchOrCancel; + } + + private void newSearch() { + searchNumber = (searchNumber + 1) % 1000; + } + + public void incrementTargetChecked(int amount) { + synchronized (serverFinderLock) { + if (state != ServerFinderState.CANCELLED) + targetChecked += amount; + } + } + + public ServerFinderState getState() { + return state; + } + + private void searchOrCancel() { + if (state.isRunning()) { + state = ServerFinderState.CANCELLED; + return; + } + + state = ServerFinderState.RESOLVING; + maxThreads = maxThreadsBox.get(); + ipsToPing.clear(); + targetChecked = 1792; + numActiveThreads = 0; + checked = 0; + working = 0; + + newSearch(); + + parseVersionFilters(); + + findServers(); + } + + private void parseVersionFilters() { + String filter = versionBox.get(); + String[] versions = filter.split(";"); + if (versionFilters == null) { + versionFilters = new ArrayList<>(); + } + versionFilters.clear(); + for (String version : versions) { + String trimmed = version.trim(); + if (trimmed.length() > 0) + versionFilters.add(version.trim()); + } + } + + private void findServers() { + try { + InetAddress addr = InetAddress.getByName(ipBox.get().split(":")[0].trim()); + + int[] ipParts = new int[4]; + for (int i = 0; i < 4; i++) + ipParts[i] = addr.getAddress()[i] & 0xff; + + state = ServerFinderState.SEARCHING; + int[] changes = {0, 1, -1, 2, -2, 3, -3}; + for (int change : changes) + for (int i2 = 0; i2 <= 255; i2++) { + if (state == ServerFinderState.CANCELLED) + return; + + int[] ipParts2 = ipParts.clone(); + ipParts2[2] = ipParts[2] + change & 0xff; + ipParts2[3] = i2; + String ip = ipParts2[0] + "." + ipParts2[1] + "." + ipParts2[2] + "." + ipParts2[3]; + + ipsToPing.push(ip); + } + while (numActiveThreads < maxThreads && pingNewIP()) { + } + + } catch (UnknownHostException e) { + state = ServerFinderState.UNKNOWN_HOST; + + } catch (Exception e) { + e.printStackTrace(); + state = ServerFinderState.ERROR; + } + } + + private boolean pingNewIP() { + synchronized (serverFinderLock) { + if (ipsToPing.size() > 0) { + String ip = ipsToPing.pop(); + ServerPinger pinger = new ServerPinger(scanPortsBox.checked, searchNumber); + pinger.addServerFinderDoneListener(this); + pinger.ping(ip); + numActiveThreads++; + return true; + } + } + return false; + } + + @Override + public void tick() { + searchButton.set(state.isRunning() ? "Cancel" : "Search"); + if (state.isRunning()) { + ipBox.setFocused(false); + maxThreadsBox.set(maxThreads); + } + stateLabel.set(state.toString()); + checkedLabel.set("Checked: " + checked + " / " + targetChecked); + workingLabel.set("Working: " + working); + searchButton.visible = !ipBox.get().isEmpty(); + } + + private boolean isServerInList(String ip) { + for (int i = 0; i < multiplayerScreen.getServerList().size(); i++) + if (multiplayerScreen.getServerList().get(i).address.equals(ip)) + return true; + + return false; + } + + @Override + public void close() { + state = ServerFinderState.CANCELLED; + super.close(); + } + + private boolean filterPass(MServerInfo info) { + if (info == null) + return false; + if (info.playerCount < playerCountFilter) + return false; + for (String version : versionFilters) { + if (info.version != null && info.version.contains(version)) { + return true; + } + } + return versionFilters.isEmpty(); + } + + @Override + public void onServerDone(ServerPinger pinger) { + if (state == ServerFinderState.CANCELLED || pinger == null || pinger.getSearchNumber() != searchNumber) + return; + synchronized (serverFinderLock) { + checked++; + numActiveThreads--; + } + if (pinger.isWorking()) { + if (!isServerInList(pinger.getServerIP()) && filterPass(pinger.getServerInfo())) { + synchronized (serverFinderLock) { + working++; + multiplayerScreen.getServerList().add(new ServerInfo("Server discovery #" + working, pinger.getServerIP(), false), false); + multiplayerScreen.getServerList().saveFile(); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget().setSelected(null); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget().setServers(multiplayerScreen.getServerList()); + } + } + } + while (numActiveThreads < maxThreads && pingNewIP()) ; + synchronized (serverFinderLock) { + if (checked == targetChecked) { + state = ServerFinderState.DONE; + } + } + } + + @Override + public void onServerFailed(ServerPinger pinger) { + if (state == ServerFinderState.CANCELLED || pinger == null || pinger.getSearchNumber() != searchNumber) + return; + synchronized (serverFinderLock) { + checked++; + numActiveThreads--; + } + while (numActiveThreads < maxThreads && pingNewIP()) ; + synchronized (serverFinderLock) { + if (checked == targetChecked) { + state = ServerFinderState.DONE; + } + } + } + + public enum ServerFinderState { + NOT_RUNNING(""), + SEARCHING("Searching..."), + RESOLVING("Resolving..."), + UNKNOWN_HOST("Unknown Host!"), + CANCELLED("Cancelled!"), + DONE("Done!"), + ERROR("An error occurred!"); + + private final String name; + + ServerFinderState(String name) { + this.name = name; + } + + public boolean isRunning() { + return this == SEARCHING || this == RESOLVING; + } + + @Override + public String toString() { + return name; + } + } +} diff --git a/src/main/java/anticope/rejects/gui/servers/ServerManagerScreen.java b/src/main/java/anticope/rejects/gui/servers/ServerManagerScreen.java new file mode 100644 index 0000000..bda7b3c --- /dev/null +++ b/src/main/java/anticope/rejects/gui/servers/ServerManagerScreen.java @@ -0,0 +1,161 @@ +package anticope.rejects.gui.servers; + +import anticope.rejects.MeteorRejectsAddon; +import anticope.rejects.mixin.MultiplayerScreenAccessor; +import anticope.rejects.mixin.ServerListAccessor; +import anticope.rejects.utils.server.IPAddress; +import meteordevelopment.meteorclient.gui.GuiTheme; +import meteordevelopment.meteorclient.gui.WindowScreen; +import meteordevelopment.meteorclient.gui.widgets.containers.WContainer; +import meteordevelopment.meteorclient.gui.widgets.containers.WHorizontalList; +import meteordevelopment.meteorclient.gui.widgets.pressable.WButton; +import meteordevelopment.meteorclient.utils.misc.IGetter; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.option.ServerList; +import net.minecraft.client.toast.SystemToast; +import net.minecraft.text.Text; +import org.lwjgl.BufferUtils; +import org.lwjgl.PointerBuffer; +import org.lwjgl.system.MemoryUtil; +import org.lwjgl.util.tinyfd.TinyFileDialogs; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Consumer; + +public class ServerManagerScreen extends WindowScreen { + + private static final PointerBuffer saveFileFilters; + + static { + saveFileFilters = BufferUtils.createPointerBuffer(1); + saveFileFilters.put(MemoryUtil.memASCII("*.txt")); + saveFileFilters.rewind(); + } + + private final MultiplayerScreen multiplayerScreen; + + public ServerManagerScreen(GuiTheme theme, MultiplayerScreen multiplayerScreen) { + super(theme, "Manage Servers"); + this.parent = multiplayerScreen; + this.multiplayerScreen = multiplayerScreen; + } + + public static Runnable tryHandle(ThrowingRunnable tr, Consumer handler) { + return Objects.requireNonNull(tr).addHandler(handler); + } + + @Override + public void initWidgets() { + WHorizontalList l = add(theme.horizontalList()).expandX().widget(); + addButton(l, "Find Servers (new)", () -> new ServerFinderScreen(theme, multiplayerScreen, this)); + addButton(l, "Find Servers (legacy)", () -> new LegacyServerFinderScreen(theme, multiplayerScreen, this)); + addButton(l, "Clean Up", () -> new CleanUpScreen(theme, multiplayerScreen, this)); + l = add(theme.horizontalList()).expandX().widget(); + l.add(theme.button("Save IPs")).expandX().widget().action = tryHandle(() -> { + String targetPath = TinyFileDialogs.tinyfd_saveFileDialog("Save IPs", null, saveFileFilters, null); + if (targetPath == null) return; + if (!targetPath.endsWith(".txt")) targetPath += ".txt"; + Path filePath = Path.of(targetPath); + + int newIPs = 0; + + Set hashedIPs = new HashSet<>(); + if (Files.exists(filePath)) { + try { + List ips = Files.readAllLines(filePath); + for (String ip : ips) { + IPAddress parsedIP = IPAddress.fromText(ip); + if (parsedIP != null) + hashedIPs.add(parsedIP); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + ServerList servers = multiplayerScreen.getServerList(); + for (int i = 0; i < servers.size(); i++) { + ServerInfo info = servers.get(i); + IPAddress addr = IPAddress.fromText(info.address); + if (addr != null && hashedIPs.add(addr)) + newIPs++; + } + + StringBuilder fileOutput = new StringBuilder(); + for (IPAddress ip : hashedIPs) { + String stringIP = ip.toString(); + if (stringIP != null) + fileOutput.append(stringIP).append("\n"); + } + + try { + Files.writeString(filePath, fileOutput.toString()); + } catch (IOException e) { + e.printStackTrace(); + } + + toast("Success!", newIPs == 1 ? "Saved %s new IP" : "Saved %s new IPs", newIPs); + }, e -> { + MeteorRejectsAddon.LOG.error("Could not save IPs"); + toast("Something went wrong", "The IPs could not be saved, look at the log for details"); + }); + l.add(theme.button("Load IPs")).expandX().widget().action = tryHandle(() -> { + String targetPath = TinyFileDialogs.tinyfd_openFileDialog("Load IPs", null, saveFileFilters, "", false); + if (targetPath == null) return; + Path filePath = Path.of(targetPath); + if (!Files.exists(filePath)) return; + + List servers = ((ServerListAccessor) multiplayerScreen.getServerList()).getServers(); + Set presentAddresses = new HashSet<>(); + int newIPs = 0; + for (ServerInfo server : servers) presentAddresses.add(server.address); + for (String addr : MinecraftClient.getInstance().keyboard.getClipboard().split("[\r\n]+")) { + if (presentAddresses.add(addr = addr.split(" ")[0])) { + servers.add(new ServerInfo("Server discovery #" + presentAddresses.size(), addr, false)); + newIPs++; + } + } + multiplayerScreen.getServerList().saveFile(); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget().setSelected(null); + ((MultiplayerScreenAccessor) multiplayerScreen).getServerListWidget().setServers(multiplayerScreen.getServerList()); + toast("Success!", newIPs == 1 ? "Loaded %s new IP" : "Loaded %s new IPs", newIPs); + }, e -> { + MeteorRejectsAddon.LOG.error("Could not load IPs"); + toast("Something went wrong", "The IPs could not be loaded, look at the log for details"); + }); + } + + private void toast(String titleKey, String descriptionKey, Object... params) { + SystemToast.add(client.getToastManager(), SystemToast.Type.WORLD_BACKUP, Text.literal(titleKey), Text.translatable(descriptionKey, params)); + } + + private void addButton(WContainer c, String text, IGetter action) { + WButton button = c.add(theme.button(text)).expandX().widget(); + button.action = () -> client.setScreen(action.get()); + } + + public interface ThrowingRunnable { + void run() throws TEx; + + default Runnable addHandler(Consumer handler) { + Objects.requireNonNull(handler); + return () -> { + try { + this.run(); + } catch (Throwable var3) { + handler.accept(var3); + } + }; + } + } + +} diff --git a/src/main/java/anticope/rejects/mixin/MultiplayerScreenAccessor.java b/src/main/java/anticope/rejects/mixin/MultiplayerScreenAccessor.java new file mode 100644 index 0000000..16361ef --- /dev/null +++ b/src/main/java/anticope/rejects/mixin/MultiplayerScreenAccessor.java @@ -0,0 +1,12 @@ +package anticope.rejects.mixin; + +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerServerListWidget; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(MultiplayerScreen.class) +public interface MultiplayerScreenAccessor { + @Accessor("serverListWidget") + MultiplayerServerListWidget getServerListWidget(); +} diff --git a/src/main/java/anticope/rejects/mixin/MultiplayerScreenMixin.java b/src/main/java/anticope/rejects/mixin/MultiplayerScreenMixin.java new file mode 100644 index 0000000..2c32c1a --- /dev/null +++ b/src/main/java/anticope/rejects/mixin/MultiplayerScreenMixin.java @@ -0,0 +1,26 @@ +package anticope.rejects.mixin; + +import anticope.rejects.gui.servers.ServerManagerScreen; +import meteordevelopment.meteorclient.gui.GuiThemes; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.multiplayer.MultiplayerScreen; +import net.minecraft.client.gui.widget.ButtonWidget; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(MultiplayerScreen.class) +public abstract class MultiplayerScreenMixin extends Screen { + protected MultiplayerScreenMixin(Text title) { + super(title); + } + + @Inject(method = "init", at = @At("TAIL")) + private void onInit(CallbackInfo info) { + addDrawableChild(new ButtonWidget(this.width - 75 - 3 - 75 - 2 - 75 - 2, 3, 75, 20, Text.literal("Servers"), button -> { + client.setScreen(new ServerManagerScreen(GuiThemes.get(), (MultiplayerScreen) (Object) this)); + })); + } +} diff --git a/src/main/java/anticope/rejects/mixin/ServerListAccessor.java b/src/main/java/anticope/rejects/mixin/ServerListAccessor.java new file mode 100644 index 0000000..21fba66 --- /dev/null +++ b/src/main/java/anticope/rejects/mixin/ServerListAccessor.java @@ -0,0 +1,14 @@ +package anticope.rejects.mixin; + +import net.minecraft.client.network.ServerInfo; +import net.minecraft.client.option.ServerList; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +import java.util.List; + +@Mixin(ServerList.class) +public interface ServerListAccessor { + @Accessor + List getServers(); +} diff --git a/src/main/java/anticope/rejects/modules/ExtraElytra.java b/src/main/java/anticope/rejects/modules/ExtraElytra.java index 91dc464..a2fdb16 100644 --- a/src/main/java/anticope/rejects/modules/ExtraElytra.java +++ b/src/main/java/anticope/rejects/modules/ExtraElytra.java @@ -1,7 +1,6 @@ package anticope.rejects.modules; import anticope.rejects.MeteorRejectsAddon; -import meteordevelopment.meteorclient.MeteorClient; import meteordevelopment.meteorclient.events.world.TickEvent; import meteordevelopment.meteorclient.settings.BoolSetting; import meteordevelopment.meteorclient.settings.Setting; @@ -58,12 +57,6 @@ public class ExtraElytra extends Module { @Override public void onActivate() { jumpTimer = 0; - MeteorClient.EVENT_BUS.subscribe(this); - } - - @Override - public void onDeactivate() { - MeteorClient.EVENT_BUS.unsubscribe(this); } public ExtraElytra() { diff --git a/src/main/java/anticope/rejects/modules/ItemGenerator.java b/src/main/java/anticope/rejects/modules/ItemGenerator.java new file mode 100644 index 0000000..1b47a83 --- /dev/null +++ b/src/main/java/anticope/rejects/modules/ItemGenerator.java @@ -0,0 +1,68 @@ +package anticope.rejects.modules; + +import anticope.rejects.MeteorRejectsAddon; +import meteordevelopment.orbit.EventHandler; +import meteordevelopment.meteorclient.events.world.TickEvent; +import meteordevelopment.meteorclient.settings.IntSetting; +import meteordevelopment.meteorclient.settings.Setting; +import meteordevelopment.meteorclient.settings.SettingGroup; +import meteordevelopment.meteorclient.systems.modules.Module; +import meteordevelopment.meteorclient.utils.player.InvUtils; +import net.minecraft.item.ItemStack; +import net.minecraft.item.Items; +import net.minecraft.network.packet.c2s.play.CreativeInventoryActionC2SPacket; +import net.minecraft.util.math.random.Random; +import net.minecraft.util.registry.Registry; +import net.minecraft.util.registry.RegistryEntry; + +public class ItemGenerator extends Module { + private final SettingGroup sgGeneral = settings.getDefaultGroup(); + private final Setting speed = sgGeneral.add(new IntSetting.Builder() + .name("speed") + .description("\u00a74\u00a7lWARNING:\u00a7r High speeds will cause a ton\n" + + "of lag and can easily crash the game!") + .defaultValue(1) + .min(1) + .max(36) + .sliderMax(36) + .build() + ); + + private final Setting stackSize = sgGeneral.add(new IntSetting.Builder() + .name("stack-size") + .description("How many items to place in each stack.\n" + + "Doesn't seem to affect performance.") + .defaultValue(1) + .min(1) + .max(64) + .sliderMax(64) + .build() + ); + + private final Random random = Random.create(); + + public ItemGenerator() { + super(MeteorRejectsAddon.CATEGORY, "item-generator", "Spawns a lot of unwanted items"); + } + + @Override + public void onActivate() { + if(!mc.player.getAbilities().creativeMode) { + error("Creative mode only."); + this.toggle(); + } + } + + @EventHandler + private void onTick(TickEvent.Post event) { + int stacks = speed.get(); + int size = stackSize.get(); + for(int i = 9; i < 9 + stacks; i++) { + mc.player.networkHandler.sendPacket(new CreativeInventoryActionC2SPacket(i, new ItemStack(Registry.ITEM.getRandom(random).map(RegistryEntry::value).orElse(Items.DIRT), size))); + } + + for(int i = 9; i < 9 + stacks; i++) { + InvUtils.drop().slot(i); + } + } +} diff --git a/src/main/java/anticope/rejects/utils/server/IPAddress.java b/src/main/java/anticope/rejects/utils/server/IPAddress.java new file mode 100644 index 0000000..16c4247 --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/IPAddress.java @@ -0,0 +1,92 @@ +package anticope.rejects.utils.server; + +public class IPAddress { + private final int[] octets; + private final int port; + + public IPAddress(int o1, int o2, int o3, int o4, int port) { + this.octets = new int[]{o1, o2, o3, o4}; + this.port = port; + } + + public IPAddress(int[] octets, int port) { + this.octets = octets; + this.port = port; + } + + public static IPAddress fromText(String ip) { + String[] sections = ip.split(":"); + if (sections.length < 1 || sections.length > 2) + return null; + + int port = 25565; + if (sections.length == 2) { + try { + port = Integer.parseInt(sections[1].trim()); + } catch (NumberFormatException e) { + return null; + } + } + + int[] octets = new int[4]; + + String[] address = sections[0].trim().split("\\."); + if (address.length != 4) + return null; + + for (int i = 0; i < 4; i++) { + try { + octets[i] = Integer.parseInt(address[i].trim()); + } catch (NumberFormatException e) { + return null; + } + } + + return new IPAddress(octets, port); + } + + @Override + public boolean equals(Object o) { + if (!(o instanceof IPAddress other)) + return false; + + if (octets.length != other.octets.length) + return false; + + if (port != other.port) + return false; + + for (int i = 0; i < octets.length; i++) + if (octets[i] != other.octets[i]) + return false; + + return true; + } + + @Override + public int hashCode() { + assert (octets.length == 4); + + int hash = 43; + hash = hash * 59 + octets[0]; + hash = hash * 83 + octets[1]; + hash = hash * 71 + octets[2]; + hash = hash * 17 + octets[3]; + hash = hash * 31 + port; + return hash; + } + + @Override + public String toString() { + if (octets.length == 0) + return null; + + StringBuilder result = new StringBuilder(); + result.append(octets[0]); + for (int i = 1; i < octets.length; i++) { + result.append('.').append(octets[i]); + } + result.append(':').append(port); + return result.toString(); + } +} diff --git a/src/main/java/anticope/rejects/utils/server/IServerFinderDisconnectListener.java b/src/main/java/anticope/rejects/utils/server/IServerFinderDisconnectListener.java new file mode 100644 index 0000000..47764e0 --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/IServerFinderDisconnectListener.java @@ -0,0 +1,7 @@ +package anticope.rejects.utils.server; + +public interface IServerFinderDisconnectListener { + void onServerDisconnect(); + + void onServerFailed(); +} diff --git a/src/main/java/anticope/rejects/utils/server/IServerFinderDoneListener.java b/src/main/java/anticope/rejects/utils/server/IServerFinderDoneListener.java new file mode 100644 index 0000000..eaa05ed --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/IServerFinderDoneListener.java @@ -0,0 +1,7 @@ +package anticope.rejects.utils.server; + +public interface IServerFinderDoneListener { + void onServerDone(ServerPinger pinger); + + void onServerFailed(ServerPinger pinger); +} diff --git a/src/main/java/anticope/rejects/utils/server/LegacyServerPinger.java b/src/main/java/anticope/rejects/utils/server/LegacyServerPinger.java new file mode 100644 index 0000000..2f68c8a --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/LegacyServerPinger.java @@ -0,0 +1,60 @@ +package anticope.rejects.utils.server; + +import anticope.rejects.MeteorRejectsAddon; +import net.minecraft.client.network.MultiplayerServerListPinger; +import net.minecraft.client.network.ServerInfo; + +import java.net.UnknownHostException; +import java.util.concurrent.atomic.AtomicInteger; + +public class LegacyServerPinger { + private static final AtomicInteger threadNumber = new AtomicInteger(0); + private ServerInfo server; + private boolean done = false; + private boolean failed = false; + + public void ping(String ip) { + ping(ip, 25565); + } + + public void ping(String ip, int port) { + server = new ServerInfo("", ip + ":" + port, false); + + new Thread(() -> pingInCurrentThread(ip, port), + "Server Pinger #" + threadNumber.incrementAndGet()).start(); + } + + private void pingInCurrentThread(String ip, int port) { + MultiplayerServerListPinger pinger = new MultiplayerServerListPinger(); + MeteorRejectsAddon.LOG.info("Pinging " + ip + ":" + port + "..."); + + try { + pinger.add(server, () -> { + }); + MeteorRejectsAddon.LOG.info("Ping successful: " + ip + ":" + port); + + } catch (UnknownHostException e) { + MeteorRejectsAddon.LOG.warn("Unknown host: " + ip + ":" + port); + failed = true; + + } catch (Exception e2) { + MeteorRejectsAddon.LOG.warn("Ping failed: " + ip + ":" + port); + failed = true; + } + + pinger.cancel(); + done = true; + } + + public boolean isStillPinging() { + return !done; + } + + public boolean isWorking() { + return !failed; + } + + public String getServerIP() { + return server.address; + } +} diff --git a/src/main/java/anticope/rejects/utils/server/MServerInfo.java b/src/main/java/anticope/rejects/utils/server/MServerInfo.java new file mode 100644 index 0000000..dbeb8a5 --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/MServerInfo.java @@ -0,0 +1,37 @@ +package anticope.rejects.utils.server; + +import net.minecraft.SharedConstants; +import net.minecraft.text.Text; +import org.jetbrains.annotations.Nullable; + +import java.util.Collections; +import java.util.List; + +public class MServerInfo { + public String name; + public String address; + public String playerCountLabel; + public int playerCount; + public int playercountMax; + public String label; + public long ping; + public int protocolVersion = SharedConstants.getGameVersion().getProtocolVersion(); + public String version = null; + public List playerListSummary = Collections.emptyList(); + @Nullable + private String icon; + + public MServerInfo(String name, String address) { + this.name = name; + this.address = address; + } + + @Nullable + public String getIcon() { + return this.icon; + } + + public void setIcon(@Nullable String string) { + this.icon = string; + } +} diff --git a/src/main/java/anticope/rejects/utils/server/ServerListPinger.java b/src/main/java/anticope/rejects/utils/server/ServerListPinger.java new file mode 100644 index 0000000..2f8b400 --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/ServerListPinger.java @@ -0,0 +1,290 @@ +package anticope.rejects.utils.server; + +import com.google.common.base.Splitter; +import com.google.common.collect.Iterables; +import com.google.common.collect.Lists; +import com.mojang.authlib.GameProfile; +import io.netty.bootstrap.Bootstrap; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.channel.*; +import io.netty.channel.socket.nio.NioSocketChannel; +import net.minecraft.client.network.ServerAddress; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.NetworkState; +import net.minecraft.network.listener.ClientQueryPacketListener; +import net.minecraft.network.packet.c2s.handshake.HandshakeC2SPacket; +import net.minecraft.network.packet.c2s.query.QueryPingC2SPacket; +import net.minecraft.network.packet.c2s.query.QueryRequestC2SPacket; +import net.minecraft.network.packet.s2c.query.QueryPongS2CPacket; +import net.minecraft.network.packet.s2c.query.QueryResponseS2CPacket; +import net.minecraft.server.ServerMetadata; +import net.minecraft.text.Text; +import net.minecraft.util.Util; +import net.minecraft.util.math.MathHelper; +import org.apache.commons.lang3.ArrayUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +import java.net.InetAddress; +import java.net.InetSocketAddress; +import java.net.UnknownHostException; +import java.nio.charset.StandardCharsets; +import java.util.*; + +public class ServerListPinger { + private static final Splitter ZERO_SPLITTER = Splitter.on('\u0000').limit(6); + private static final Logger LOGGER = LogManager.getLogger(); + private final List clientConnections = Collections.synchronizedList(Lists.newArrayList()); + private final ArrayList disconnectListeners = new ArrayList<>(); + private boolean notifiedDisconnectListeners = false; + private boolean failedToConnect = true; + + private static String getPlayerCountLabel(int i, int j) { + return i + "/" + j; + } + + public void addServerFinderDisconnectListener(IServerFinderDisconnectListener listener) { + disconnectListeners.add(listener); + } + + private void notifyDisconnectListeners() { + synchronized (this) { + if (!notifiedDisconnectListeners) { + notifiedDisconnectListeners = true; + for (IServerFinderDisconnectListener l : disconnectListeners) { + if (l != null) { + if (failedToConnect) { + l.onServerFailed(); + } else { + l.onServerDisconnect(); + } + } + } + } + } + } + + public void add(final MServerInfo entry, final Runnable runnable) throws UnknownHostException { + Timer timeoutTimer = new Timer(); + ServerAddress serverAddress = ServerAddress.parse(entry.address); + timeoutTimer.schedule(new TimerTask() { + @Override + public void run() { + notifyDisconnectListeners(); + } + }, 20000); + final ClientConnection clientConnection = ClientConnection.connect(new InetSocketAddress(InetAddress.getByName(serverAddress.getAddress()), serverAddress.getPort()), false); + failedToConnect = false; + this.clientConnections.add(clientConnection); + entry.label = "multiplayer.status.pinging"; + entry.ping = -1L; + entry.playerListSummary = null; + clientConnection.setPacketListener(new ClientQueryPacketListener() { + private boolean sentQuery; + private boolean received; + private long startTime; + + public void onResponse(QueryResponseS2CPacket packet) { + if (this.received) { + clientConnection.disconnect(Text.translatable("multiplayer.status.unrequested")); + } else { + this.received = true; + ServerMetadata serverMetadata = packet.getServerMetadata(); + if (serverMetadata.getDescription() != null) { + entry.label = serverMetadata.getDescription().getString(); + } else { + entry.label = ""; + } + + if (serverMetadata.getVersion() != null) { + entry.version = serverMetadata.getVersion().getGameVersion(); + entry.protocolVersion = serverMetadata.getVersion().getProtocolVersion(); + } else { + entry.version = "multiplayer.status.old"; + entry.protocolVersion = 0; + } + + if (serverMetadata.getPlayers() != null) { + entry.playerCountLabel = ServerListPinger.getPlayerCountLabel(serverMetadata.getPlayers().getOnlinePlayerCount(), serverMetadata.getPlayers().getPlayerLimit()); + entry.playerCount = serverMetadata.getPlayers().getOnlinePlayerCount(); + entry.playercountMax = serverMetadata.getPlayers().getPlayerLimit(); + List list = Lists.newArrayList(); + if (ArrayUtils.isNotEmpty(serverMetadata.getPlayers().getSample())) { + GameProfile[] var4 = serverMetadata.getPlayers().getSample(); + + for (GameProfile gameProfile : var4) { + list.add(Text.literal(gameProfile.getName())); + } + + if (serverMetadata.getPlayers().getSample().length < serverMetadata.getPlayers().getOnlinePlayerCount()) { + list.add(Text.translatable("multiplayer.status.and_more", serverMetadata.getPlayers().getOnlinePlayerCount() - serverMetadata.getPlayers().getSample().length)); + } + + entry.playerListSummary = list; + } + } else { + entry.playerCountLabel = "multiplayer.status.unknown"; + } + + String string = null; + if (serverMetadata.getFavicon() != null) { + String string2 = serverMetadata.getFavicon(); + if (string2.startsWith("data:image/png;base64,")) { + string = string2.substring("data:image/png;base64," .length()); + } else { + ServerListPinger.LOGGER.error("Invalid server icon (unknown format)"); + } + } + + if (!Objects.equals(string, entry.getIcon())) { + entry.setIcon(string); + runnable.run(); + } + + this.startTime = Util.getMeasuringTimeMs(); + clientConnection.send(new QueryPingC2SPacket(this.startTime)); + this.sentQuery = true; + notifyDisconnectListeners(); + } + } + + public void onPong(QueryPongS2CPacket packet) { + long l = this.startTime; + long m = Util.getMeasuringTimeMs(); + entry.ping = m - l; + clientConnection.disconnect(Text.translatable("multiplayer.status.finished")); + } + + public void onDisconnected(Text reason) { + if (!this.sentQuery) { + ServerListPinger.LOGGER.error("Can't ping {}: {}", entry.address, reason.getString()); + entry.label = "multiplayer.status.cannot_connect"; + entry.playerCountLabel = ""; + entry.playerCount = 0; + entry.playercountMax = 0; + ServerListPinger.this.ping(entry); + } + notifyDisconnectListeners(); + } + + public ClientConnection getConnection() { + return clientConnection; + } + }); + + try { + clientConnection.send(new HandshakeC2SPacket(serverAddress.getAddress(), serverAddress.getPort(), NetworkState.STATUS)); + clientConnection.send(new QueryRequestC2SPacket()); + } catch (Throwable var6) { + LOGGER.error("Couldn't send handshake", var6); + } + } + + private void ping(final MServerInfo serverInfo) { + final ServerAddress serverAddress = ServerAddress.parse(serverInfo.address); + (new Bootstrap()).group(ClientConnection.CLIENT_IO_GROUP.get()).handler(new ChannelInitializer<>() { + protected void initChannel(Channel channel) { + try { + channel.config().setOption(ChannelOption.TCP_NODELAY, true); + } catch (ChannelException var3) { + } + + channel.pipeline().addLast(new SimpleChannelInboundHandler() { + public void channelActive(ChannelHandlerContext channelHandlerContext) throws Exception { + super.channelActive(channelHandlerContext); + ByteBuf byteBuf = Unpooled.buffer(); + + try { + byteBuf.writeByte(254); + byteBuf.writeByte(1); + byteBuf.writeByte(250); + char[] cs = "MC|PingHost" .toCharArray(); + byteBuf.writeShort(cs.length); + char[] var4 = cs; + int var5 = cs.length; + + int var6; + char d; + for (var6 = 0; var6 < var5; ++var6) { + d = var4[var6]; + byteBuf.writeChar(d); + } + + byteBuf.writeShort(7 + 2 * serverAddress.getAddress().length()); + byteBuf.writeByte(127); + cs = serverAddress.getAddress().toCharArray(); + byteBuf.writeShort(cs.length); + var4 = cs; + var5 = cs.length; + + for (var6 = 0; var6 < var5; ++var6) { + d = var4[var6]; + byteBuf.writeChar(d); + } + + byteBuf.writeInt(serverAddress.getPort()); + channelHandlerContext.channel().writeAndFlush(byteBuf).addListener(ChannelFutureListener.CLOSE_ON_FAILURE); + } finally { + byteBuf.release(); + } + } + + protected void channelRead0(ChannelHandlerContext channelHandlerContext, ByteBuf byteBuf) { + short s = byteBuf.readUnsignedByte(); + if (s == 255) { + String string = new String(byteBuf.readBytes(byteBuf.readShort() * 2).array(), StandardCharsets.UTF_16BE); + String[] strings = Iterables.toArray(ServerListPinger.ZERO_SPLITTER.split(string), String.class); + if ("ยง1" .equals(strings[0])) { + String string2 = strings[2]; + String string3 = strings[3]; + int j = MathHelper.parseInt(strings[4], -1); + int k = MathHelper.parseInt(strings[5], -1); + serverInfo.protocolVersion = -1; + serverInfo.version = string2; + serverInfo.label = string3; + serverInfo.playerCountLabel = ServerListPinger.getPlayerCountLabel(j, k); + } + } + + channelHandlerContext.close(); + } + + public void exceptionCaught(ChannelHandlerContext channelHandlerContext, Throwable throwable) { + channelHandlerContext.close(); + } + }); + } + }).channel(NioSocketChannel.class).connect(serverAddress.getAddress(), serverAddress.getPort()); + } + + public void tick() { + synchronized (this.clientConnections) { + Iterator iterator = this.clientConnections.iterator(); + + while (iterator.hasNext()) { + ClientConnection clientConnection = iterator.next(); + if (clientConnection.isOpen()) { + clientConnection.tick(); + } else { + iterator.remove(); + clientConnection.handleDisconnection(); + } + } + } + } + + public void cancel() { + synchronized (this.clientConnections) { + Iterator iterator = this.clientConnections.iterator(); + + while (iterator.hasNext()) { + ClientConnection clientConnection = iterator.next(); + if (clientConnection.isOpen()) { + iterator.remove(); + clientConnection.disconnect(Text.translatable("multiplayer.status.cancelled")); + } + } + } + } +} diff --git a/src/main/java/anticope/rejects/utils/server/ServerPinger.java b/src/main/java/anticope/rejects/utils/server/ServerPinger.java new file mode 100644 index 0000000..7dfb00c --- /dev/null +++ b/src/main/java/anticope/rejects/utils/server/ServerPinger.java @@ -0,0 +1,213 @@ +package anticope.rejects.utils.server; + +import anticope.rejects.gui.servers.ServerFinderScreen; + +import java.net.UnknownHostException; +import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; + +public class ServerPinger implements IServerFinderDoneListener, IServerFinderDisconnectListener { + private static final AtomicInteger threadNumber = new AtomicInteger(0); + private final Object portPingerLock = new Object(); + private MServerInfo server; + private boolean done = false; + private boolean failed = false; + private Thread thread; + private int pingPort; + private ServerListPinger pinger; + private boolean notifiedDoneListeners = false; + private boolean scanPorts; + private int searchNumber; + private int currentIncrement = 1; + private boolean startingIncrement = true; + private ArrayList doneListeners = new ArrayList<>(); + private int portPingers = 0; + private int successfulPortPingers = 0; + private String pingIP; + + public ServerPinger(boolean scanPorts, int searchNumber) { + pinger = new ServerListPinger(); + pinger.addServerFinderDisconnectListener(this); + this.scanPorts = scanPorts; + this.searchNumber = searchNumber; + } + + public void addServerFinderDoneListener(IServerFinderDoneListener listener) { + doneListeners.add(listener); + } + + public void ping(String ip) { + ping(ip, 25565); + } + + public int getSearchNumber() { + return searchNumber; + } + + public Thread getThread() { + return thread; + } + + public int getPingPort() { + return pingPort; + } + + public MServerInfo getServerInfo() { + return server; + } + + public void ping(String ip, int port) { + if (isOldSearch()) + return; + + pingIP = ip; + pingPort = port; + server = new MServerInfo("", ip + ":" + port); + server.version = null; + + if (scanPorts) { + thread = new Thread(() -> pingInCurrentThread(ip, port), + "Server Pinger #" + threadNumber.incrementAndGet()); + } else { + thread = new Thread(() -> pingInCurrentThread(ip, port), + "Server Pinger #" + threadNumber + ", " + port); + } + thread.start(); + } + + public ServerListPinger getServerListPinger() { + return pinger; + } + + private boolean isOldSearch() { + return ServerFinderScreen.instance == null || ServerFinderScreen.instance.getState() == ServerFinderScreen.ServerFinderState.CANCELLED || ServerFinderScreen.getSearchNumber() != searchNumber; + } + + private void runPortIncrement(String ip) { + synchronized (portPingerLock) { + portPingers = 0; + successfulPortPingers = 0; + } + for (int i = startingIncrement ? 1 : currentIncrement; i < currentIncrement * 2; i++) { + if (isOldSearch()) + return; + ServerPinger pp1 = new ServerPinger(false, searchNumber); + ServerPinger pp2 = new ServerPinger(false, searchNumber); + for (IServerFinderDoneListener doneListener : doneListeners) { + pp1.addServerFinderDoneListener(doneListener); + pp2.addServerFinderDoneListener(doneListener); + } + pp1.addServerFinderDoneListener(this); + pp2.addServerFinderDoneListener(this); + if (ServerFinderScreen.instance != null && !isOldSearch()) { + ServerFinderScreen.instance.incrementTargetChecked(2); + } + pp1.ping(ip, 25565 - i); + pp2.ping(ip, 25565 + i); + } + synchronized (portPingerLock) { + currentIncrement *= 2; + } + } + + private void pingInCurrentThread(String ip, int port) { + if (isOldSearch()) + return; + + + try { + pinger.add(server, () -> { + }); + } catch (Exception e) { + failed = true; + } + + startingIncrement = true; + if (!failed) { + currentIncrement = 8; + } + + if (!failed && scanPorts) { + runPortIncrement(ip); + } + + if (failed) { + pinger.cancel(); + done = true; + notifyDoneListeners(false); + } + } + + public boolean isStillPinging() { + return !done; + } + + public boolean isWorking() { + return !failed; + } + + public boolean isOtherVersion() { + return server.protocolVersion != 47; + } + + public String getServerIP() { + return server.address; + } + + @Override + public void onServerDisconnect() { + if (isOldSearch()) + return; + + pinger.cancel(); + done = true; + notifyDoneListeners(false); + } + + private void notifyDoneListeners(boolean failure) { + synchronized (this) { + if (!notifiedDoneListeners) { + notifiedDoneListeners = true; + for (IServerFinderDoneListener doneListener : doneListeners) { + if (doneListener != null) { + if (failure) { + doneListener.onServerFailed(this); + } else { + doneListener.onServerDone(this); + } + } + } + } + } + } + + @Override + public void onServerFailed() { + if (isOldSearch()) + return; + + pinger.cancel(); + done = true; + notifyDoneListeners(true); + } + + @Override + public void onServerDone(ServerPinger pinger) { + synchronized (portPingerLock) { + portPingers += 1; + if (pinger.isWorking()) + successfulPortPingers += 1; + if (portPingers == (startingIncrement ? currentIncrement * 2 - 2 : currentIncrement) && currentIncrement <= 5000 && successfulPortPingers > 0) { + startingIncrement = false; + new Thread(() -> runPortIncrement(pingIP)).start(); + } + } + } + + @Override + public void onServerFailed(ServerPinger pinger) { + synchronized (portPingerLock) { + portPingers += 1; + } + } +} diff --git a/src/main/resources/meteor-rejects.mixins.json b/src/main/resources/meteor-rejects.mixins.json index 4a906f4..0fef3fe 100644 --- a/src/main/resources/meteor-rejects.mixins.json +++ b/src/main/resources/meteor-rejects.mixins.json @@ -14,7 +14,10 @@ "StructureVoidBlockMixin", "ToastManagerMixin", "LivingEntityMixin", - "baritone.MineProcessMixin" + "baritone.MineProcessMixin", + "MultiplayerScreenAccessor", + "MultiplayerScreenMixin", + "ServerListAccessor" ], "injectors": { "defaultRequire": 1