From c5ab2f3b04118e1dc6e5f9e75143c8dd45c8100d Mon Sep 17 00:00:00 2001 From: rttv Date: Mon, 18 May 2026 22:25:20 -0400 Subject: [PATCH 1/2] squashed --- .../clientcommands/features/Relogger.java | 122 ++++++++++++++---- ...lientHandshakePacketListenerImplMixin.java | 49 +++++++ .../assets/clientcommands/lang/en_us.json | 1 + src/main/resources/clientcommands.aw | 4 + src/main/resources/mixins.clientcommands.json | 1 + 5 files changed, 152 insertions(+), 25 deletions(-) create mode 100644 src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java diff --git a/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java b/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java index 405548fc..8fdc0a39 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java @@ -2,7 +2,11 @@ import net.earthcomputer.clientcommands.event.MoreScreenEvents; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.GuiGraphicsExtractor; +import net.minecraft.client.gui.components.Button; +import net.minecraft.client.gui.components.events.GuiEventListener; import net.minecraft.client.gui.screens.ConnectScreen; +import net.minecraft.client.gui.screens.DisconnectedScreen; import net.minecraft.client.gui.screens.GenericMessageScreen; import net.minecraft.client.gui.screens.LevelLoadingScreen; import net.minecraft.client.gui.screens.PauseScreen; @@ -14,6 +18,8 @@ import net.minecraft.client.multiplayer.ServerData; import net.minecraft.client.multiplayer.resolver.ServerAddress; import net.minecraft.client.server.IntegratedServer; +import net.minecraft.network.chat.Component; +import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.world.level.storage.LevelResource; import org.jspecify.annotations.Nullable; @@ -22,17 +28,19 @@ public class Relogger { public static boolean isRelogging; + @Nullable + public static ServerData cachedServerData; + public static int remainingTicks; public static final List relogSuccessTasks = new ArrayList<>(); + public static final String RATE_LIMIT_MESSAGE = "RateLimiter disallowed request"; + public static final int RETRY_DELAY_TICKS = 200; + static { MoreScreenEvents.BEFORE_ADD.register(Relogger::onAddScreen); } - public static boolean disconnect() { - return disconnect(false); - } - - private static boolean disconnect(boolean relogging) { + private static boolean disconnect() { Minecraft mc = Minecraft.getInstance(); if (mc.level == null) { return false; @@ -40,9 +48,7 @@ private static boolean disconnect(boolean relogging) { boolean singleplayer = mc.isLocalServer(); mc.level.disconnect(ClientLevel.DEFAULT_QUIT_MESSAGE); - if (relogging) { - isRelogging = true; - } + isRelogging = true; if (singleplayer) { mc.disconnectWithSavingScreen(); } else { @@ -62,29 +68,88 @@ private static boolean disconnect(boolean relogging) { public static boolean relog() { Minecraft mc = Minecraft.getInstance(); if (mc.isLocalServer()) { - IntegratedServer server = Minecraft.getInstance().getSingleplayerServer(); - if (server == null) { - return false; - } - String levelName = server.getWorldPath(LevelResource.ROOT).normalize().getFileName().toString(); - if (!disconnect(true)) { + IntegratedServer server = mc.getSingleplayerServer(); + if (!disconnect()) { return false; } - if (!mc.getLevelSource().levelExists(levelName)) { - return false; - } - mc.createWorldOpenFlows().openWorld(levelName, () -> mc.setScreen(new TitleScreen())); - return true; + return loginToIntegratedServer(server); } else { - ServerData serverData = mc.getCurrentServer(); - if (serverData == null) { + ServerData server = mc.getCurrentServer(); + if (!disconnect()) { return false; } - if (!disconnect(true)) { - return false; + return loginToDedicatedServer(server); + } + } + + private static boolean loginToIntegratedServer(@Nullable IntegratedServer server) { + Minecraft mc = Minecraft.getInstance(); + + if (server == null) { + return false; + } + String levelName = server.getWorldPath(LevelResource.ROOT).normalize().getFileName().toString(); + if (!mc.getLevelSource().levelExists(levelName)) { + return false; + } + mc.createWorldOpenFlows().openWorld(levelName, () -> mc.setScreen(new TitleScreen())); + return true; + } + + private static boolean loginToDedicatedServer(@Nullable ServerData serverData) { + Minecraft mc = Minecraft.getInstance(); + + if (serverData == null) { + return false; + } + isRelogging = true; + cachedServerData = serverData; + ConnectScreen.startConnecting(mc.screen, mc, ServerAddress.parseString(serverData.ip), serverData, false, null); + return true; + } + + public static void onFailedRelog() { + Minecraft mc = Minecraft.getInstance(); + // only possible if the user clicks off and "cancels" + if (!(mc.screen instanceof DisconnectedScreen screen) || cachedServerData == null || !isRateLimitMessage(screen.details.reason())) { + isRelogging = false; + cachedServerData = null; + return; + } + + mc.setScreen(screen.parent); + + ServerData serverData = cachedServerData; + cachedServerData = null; + isRelogging = false; + loginToDedicatedServer(serverData); + } + + public static void onDisconnectScreenRender(Screen screen, GuiGraphicsExtractor graphics, int mouseX, int mouseY, float tickProgress) { + Button button = null; + for (GuiEventListener child : screen.children()) { + if (child instanceof Button buttonChild) { + button = buttonChild; + break; + } + } + if (button == null) { + throw new IllegalStateException("Expected a back button in the DisconnectScreen"); + } + + double remainingSeconds = (double) remainingTicks * 0.050; + Component text = Component.translatable("commands.crelog.retry", String.format("%.1f", remainingSeconds)); + int textWidth = screen.getFont().width(text); + + graphics.text(screen.getFont(), text, (screen.width - textWidth) / 2, button.getY() + button.getHeight() + 10, 0xff_ffffff); + } + + public static void onDisconnectScreenTick(Screen screen) { + if (remainingTicks > 0) { + remainingTicks--; + if (remainingTicks == 0) { + onFailedRelog(); } - ConnectScreen.startConnecting(mc.screen, mc, ServerAddress.parseString(serverData.ip), serverData, false, null); - return true; } } @@ -94,6 +159,7 @@ private static boolean onAddScreen(@Nullable Screen screen) { && !(screen instanceof LevelLoadingScreen) && !(screen instanceof ProgressScreen) && !(screen instanceof ConnectScreen) + && !(screen instanceof DisconnectedScreen) && !(screen instanceof PauseScreen) && !(screen instanceof TitleScreen) && !(screen instanceof JoinMultiplayerScreen) @@ -110,6 +176,12 @@ public static boolean onRelogSuccess() { task.run(); } relogSuccessTasks.clear(); + isRelogging = false; + cachedServerData = null; return result; } + + public static boolean isRateLimitMessage(Component error) { + return error.getContents() instanceof TranslatableContents translate && translate.getKey().equals("disconnect.loginFailedInfo") && translate.getArgs().length == 1 && translate.getArgs()[0].toString().equals(RATE_LIMIT_MESSAGE); + } } diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java new file mode 100644 index 00000000..20e83b0d --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java @@ -0,0 +1,49 @@ +package net.earthcomputer.clientcommands.mixin.commands.relog; + +import com.llamalad7.mixinextras.sugar.Local; +import net.earthcomputer.clientcommands.features.Relogger; +import net.fabricmc.fabric.impl.client.screen.ScreenExtensions; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.screens.DisconnectedScreen; +import net.minecraft.client.multiplayer.ClientHandshakePacketListenerImpl; +import net.minecraft.network.Connection; +import net.minecraft.network.chat.Component; +import net.minecraft.network.protocol.login.ServerboundKeyPacket; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +import javax.crypto.Cipher; + +@Mixin(ClientHandshakePacketListenerImpl.class) +public class ClientHandshakePacketListenerImplMixin { + @Shadow + @Final + private Connection connection; + + @Inject(method = "lambda$handleHello$0", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/Connection;disconnect(Lnet/minecraft/network/chat/Component;)V", shift = At.Shift.AFTER)) + private void onRelogFail(String digest, ServerboundKeyPacket setKeyPacket, Cipher decryptCipher, Cipher encryptCipher, CallbackInfo ci, @Local(name = "error") Component error) { + if (!Relogger.isRelogging) { + return; + } + + if (!Relogger.isRateLimitMessage(error)) { + Relogger.isRelogging = false; + return; + } + + Minecraft mc = Minecraft.getInstance(); + + mc.execute(() -> { + connection.handleDisconnection(); + if (mc.screen instanceof DisconnectedScreen screen && Relogger.isRateLimitMessage(screen.details.reason())) { + Relogger.remainingTicks = Relogger.RETRY_DELAY_TICKS; + ScreenExtensions.getExtensions(screen).fabric_getAfterRenderEvent().register(Relogger::onDisconnectScreenRender); + ScreenExtensions.getExtensions(screen).fabric_getAfterTickEvent().register(Relogger::onDisconnectScreenTick); + } + }); + } +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 910da5cf..86b946c5 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -273,6 +273,7 @@ "commands.cpredictbrushables.stewEffect": "%s (%s seconds)", "commands.crelog.failed": "Failed to relog", + "commands.crelog.retry": "Retrying in %s seconds...", "commands.crender.entities.success": "Entity rendering rules have been updated", diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index 644f1327..bb71b6e2 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -46,6 +46,10 @@ accessible method net/minecraft/client/renderer/GameRenderer setPostEffect (Lnet accessible field net/minecraft/client/renderer/ShaderManager compilationCache Lnet/minecraft/client/renderer/ShaderManager$CompilationCache; accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache configs Lnet/minecraft/client/renderer/ShaderManager$Configs; +# crelog +accessible field net/minecraft/client/gui/screens/DisconnectedScreen parent Lnet/minecraft/client/gui/screens/Screen; +accessible field net/minecraft/client/gui/screens/DisconnectedScreen details Lnet/minecraft/network/DisconnectionDetails; + # Game Options accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object; diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index d1743a5d..6341e460 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -75,6 +75,7 @@ "commands.fps.MinecraftMixin", "commands.generic.CommandSuggestionsMixin", "commands.glow.LivingEntityRenderStateMixin", + "commands.relog.ClientHandshakePacketListenerImplMixin", "commands.render.LevelRendererMixin", "commands.reply.ClientPacketListenerMixin", "commands.snap.MinecraftMixin", From 24610b4c1e3d24f23ca5b9c78b9cfa90ae5ffeb3 Mon Sep 17 00:00:00 2001 From: rttv Date: Tue, 19 May 2026 12:18:56 -0400 Subject: [PATCH 2/2] changes in response to Earthcomputer review --- .../net/earthcomputer/clientcommands/features/Relogger.java | 2 +- .../commands/relog/ClientHandshakePacketListenerImplMixin.java | 2 +- src/main/resources/clientcommands.aw | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java b/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java index 8fdc0a39..16cecfcc 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/Relogger.java @@ -111,7 +111,7 @@ private static boolean loginToDedicatedServer(@Nullable ServerData serverData) { public static void onFailedRelog() { Minecraft mc = Minecraft.getInstance(); // only possible if the user clicks off and "cancels" - if (!(mc.screen instanceof DisconnectedScreen screen) || cachedServerData == null || !isRateLimitMessage(screen.details.reason())) { + if (!(mc.screen instanceof DisconnectedScreen screen) || cachedServerData == null) { isRelogging = false; cachedServerData = null; return; diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java index 20e83b0d..18524865 100644 --- a/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/commands/relog/ClientHandshakePacketListenerImplMixin.java @@ -39,7 +39,7 @@ private void onRelogFail(String digest, ServerboundKeyPacket setKeyPacket, Ciphe mc.execute(() -> { connection.handleDisconnection(); - if (mc.screen instanceof DisconnectedScreen screen && Relogger.isRateLimitMessage(screen.details.reason())) { + if (mc.screen instanceof DisconnectedScreen screen && Relogger.isRateLimitMessage(error)) { Relogger.remainingTicks = Relogger.RETRY_DELAY_TICKS; ScreenExtensions.getExtensions(screen).fabric_getAfterRenderEvent().register(Relogger::onDisconnectScreenRender); ScreenExtensions.getExtensions(screen).fabric_getAfterTickEvent().register(Relogger::onDisconnectScreenTick); diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index bb71b6e2..996d98c9 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -48,7 +48,6 @@ accessible field net/minecraft/client/renderer/ShaderManager$CompilationCache co # crelog accessible field net/minecraft/client/gui/screens/DisconnectedScreen parent Lnet/minecraft/client/gui/screens/Screen; -accessible field net/minecraft/client/gui/screens/DisconnectedScreen details Lnet/minecraft/network/DisconnectionDetails; # Game Options accessible field net/minecraft/client/OptionInstance value Ljava/lang/Object;