diff --git a/gradle.properties b/gradle.properties index 88664a4dce..9192d49c51 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,13 +7,13 @@ org.gradle.parallel=true # https://www.curseforge.com/minecraft/mc-mods/fabric-api minecraft_version=1.20.4 yarn_mappings=1.20.4+build.3 -loader_version=0.15.6 +loader_version=0.15.7 #Fabric api -fabric_version=0.95.4+1.20.4 +fabric_version=0.96.4+1.20.4 # Mod Properties -mod_version = v7.41-MC1.20.4 +mod_version = v7.41.1-MC1.20.4 maven_group = net.wurstclient archives_base_name = Wurst-Client diff --git a/src/main/java/net/wurstclient/WurstClient.java b/src/main/java/net/wurstclient/WurstClient.java index b74d4733c6..03a5337434 100644 --- a/src/main/java/net/wurstclient/WurstClient.java +++ b/src/main/java/net/wurstclient/WurstClient.java @@ -11,6 +11,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.IllegalFormatException; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -57,7 +58,7 @@ public enum WurstClient public static MinecraftClient MC; public static IMinecraftClient IMC; - public static final String VERSION = "7.41"; + public static final String VERSION = "7.41.1"; public static final String MC_VERSION = "1.20.4"; private WurstAnalytics analytics; @@ -175,18 +176,29 @@ private Path createWurstFolder() return wurstFolder; } - public String translate(String key) + public String translate(String key, Object... args) { if(otfs.translationsOtf.getForceEnglish().isChecked()) - return ILanguageManager.getEnglish().get(key); + { + String string = ILanguageManager.getEnglish().get(key); + try + { + return String.format(string, args); + + }catch(IllegalFormatException e) + { + return key; + } + } + // This extra check is necessary because I18n.translate() doesn't // always return the key when the translation is missing. If the key // contains a '%', it will return "Format Error: key" instead. if(!I18n.hasTranslation(key)) return key; - return I18n.translate(key); + return I18n.translate(key, args); } public WurstAnalytics getAnalytics() diff --git a/src/main/java/net/wurstclient/altmanager/screens/AltManagerScreen.java b/src/main/java/net/wurstclient/altmanager/screens/AltManagerScreen.java index 23c489727d..3b47a38364 100644 --- a/src/main/java/net/wurstclient/altmanager/screens/AltManagerScreen.java +++ b/src/main/java/net/wurstclient/altmanager/screens/AltManagerScreen.java @@ -86,14 +86,15 @@ public AltManagerScreen(Screen prevScreen, AltManager altManager) public void init() { listGui = new ListGui(client, this, altManager.getList()); + WurstClient wurst = WurstClient.INSTANCE; Exception folderException = altManager.getFolderException(); if(folderException != null && shouldAsk) { - Text title = - Text.translatable("gui.wurst.altmanager.folder_error.title"); - Text message = Text.translatable( - "gui.wurst.altmanager.folder_error.message", folderException); + Text title = Text.literal( + wurst.translate("gui.wurst.altmanager.folder_error.title")); + Text message = Text.literal(wurst.translate( + "gui.wurst.altmanager.folder_error.message", folderException)); Text buttonText = Text.translatable("gui.done"); // This just sets shouldAsk to false and closes the message. @@ -105,9 +106,10 @@ public void init() }else if(altManager.getList().isEmpty() && shouldAsk) { - Text title = Text.translatable("gui.wurst.altmanager.empty.title"); - Text message = - Text.translatable("gui.wurst.altmanager.empty.message"); + Text title = Text + .literal(wurst.translate("gui.wurst.altmanager.empty.title")); + Text message = Text + .literal(wurst.translate("gui.wurst.altmanager.empty.message")); BooleanConsumer callback = this::confirmGenerate; ConfirmScreen screen = new ConfirmScreen(callback, title, message); diff --git a/src/main/java/net/wurstclient/mixin/ClientPlayNetworkHandlerMixin.java b/src/main/java/net/wurstclient/mixin/ClientPlayNetworkHandlerMixin.java index aec50258f9..e61a7246cc 100644 --- a/src/main/java/net/wurstclient/mixin/ClientPlayNetworkHandlerMixin.java +++ b/src/main/java/net/wurstclient/mixin/ClientPlayNetworkHandlerMixin.java @@ -46,7 +46,8 @@ private ClientPlayNetworkHandlerMixin(WurstClient wurst, public void onOnServerMetadata(ServerMetadataS2CPacket packet, CallbackInfo ci) { - if(!WurstClient.INSTANCE.isEnabled()) + WurstClient wurst = WurstClient.INSTANCE; + if(!wurst.isEnabled()) return; // Remove Mojang's dishonest warning toast on safe servers @@ -58,10 +59,10 @@ public void onOnServerMetadata(ServerMetadataS2CPacket packet, } // Add an honest warning toast on unsafe servers - MutableText title = Text.literal(ChatUtils.WURST_PREFIX).append( - Text.translatable("toast.wurst.nochatreports.unsafe_server.title")); - MutableText message = Text - .translatable("toast.wurst.nochatreports.unsafe_server.message"); + MutableText title = Text.literal(ChatUtils.WURST_PREFIX + + wurst.translate("toast.wurst.nochatreports.unsafe_server.title")); + MutableText message = Text.literal( + wurst.translate("toast.wurst.nochatreports.unsafe_server.message")); SystemToast systemToast = SystemToast.create(client, SystemToast.Type.UNSECURE_SERVER_WARNING, title, message); diff --git a/src/main/java/net/wurstclient/mixin/ControlsListWidgetMixin.java b/src/main/java/net/wurstclient/mixin/ControlsListWidgetMixin.java new file mode 100644 index 0000000000..1fc1660acb --- /dev/null +++ b/src/main/java/net/wurstclient/mixin/ControlsListWidgetMixin.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2014-2024 Wurst-Imperium and contributors. + * + * This source code is subject to the terms of the GNU General Public + * License, version 3. If a copy of the GPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt + */ +package net.wurstclient.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.gui.screen.option.ControlsListWidget; +import net.minecraft.client.gui.widget.ElementListWidget; +import net.minecraft.client.gui.widget.EntryListWidget; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; +import net.wurstclient.WurstClient; + +@Mixin(ControlsListWidget.class) +public abstract class ControlsListWidgetMixin + extends ElementListWidget +{ + public ControlsListWidgetMixin(WurstClient wurst, MinecraftClient client, + int width, int height, int y, int itemHeight) + { + super(client, width, height, y, itemHeight); + } + + /** + * Prevents Wurst's zoom keybind from being added to the controls list. + */ + @WrapOperation(at = @At(value = "INVOKE", + target = "Lnet/minecraft/client/gui/screen/option/ControlsListWidget;addEntry(Lnet/minecraft/client/gui/widget/EntryListWidget$Entry;)I", + ordinal = 1), + method = "(Lnet/minecraft/client/gui/screen/option/KeybindsScreen;Lnet/minecraft/client/MinecraftClient;)V") + private int dontAddZoomEntry(ControlsListWidget instance, + EntryListWidget.Entry entry, Operation original) + { + if(!(entry instanceof ControlsListWidget.KeyBindingEntry kbEntry)) + return original.call(instance, entry); + + Text name = kbEntry.bindingName; + if(name == null || !(name + .getContent() instanceof TranslatableTextContent trContent)) + return original.call(instance, entry); + + if(!"key.wurst.zoom".equals(trContent.getKey())) + return original.call(instance, entry); + + return 0; + } +} diff --git a/src/main/java/net/wurstclient/mixin/KeybindTextContentMixin.java b/src/main/java/net/wurstclient/mixin/KeybindTextContentMixin.java new file mode 100644 index 0000000000..909e4618e9 --- /dev/null +++ b/src/main/java/net/wurstclient/mixin/KeybindTextContentMixin.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2014-2024 Wurst-Imperium and contributors. + * + * This source code is subject to the terms of the GNU General Public + * License, version 3. If a copy of the GPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt + */ +package net.wurstclient.mixin; + +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.CallbackInfoReturnable; + +import net.minecraft.text.KeybindTextContent; +import net.minecraft.text.Text; +import net.minecraft.text.TextContent; + +@Mixin(KeybindTextContent.class) +public abstract class KeybindTextContentMixin implements TextContent +{ + @Shadow + @Final + private String key; + + /** + * Ensures that any chat messages, written books, signs, etc. cannot resolve + * Wurst-related keybinds. + * + *

+ * Fixes at least one security vulnerability affecting Minecraft 1.20 and + * later versions, where the server can detect the presence of Wurst by + * abusing Minecraft's sign editing feature. When a player edits a sign, any + * translated text and keybind text components on that sign are resolved by + * the client and sent back to the server as plain text. This allows the + * server to detect the presence of non-vanilla keybinds, such as Wurst's + * zoom keybind. + * + *

+ * It is likely that similar vulnerabilities exist or will exist in other + * parts of the game, such as chat messages and written books. Mojang has a + * long history of failing to properly secure their text component system + * (see BookHack, OP-Sign, BookDupe). Therefore it's best to cut off this + * entire attack vector at the source. + */ + @Inject(at = @At("RETURN"), + method = "getTranslated()Lnet/minecraft/text/Text;", + cancellable = true) + private void onGetTranslated(CallbackInfoReturnable cir) + { + if(key != null && key.contains("wurst")) + cir.setReturnValue(Text.literal(key)); + } +} diff --git a/src/main/java/net/wurstclient/mixin/TranslatableTextContentMixin.java b/src/main/java/net/wurstclient/mixin/TranslatableTextContentMixin.java new file mode 100644 index 0000000000..e144cb196b --- /dev/null +++ b/src/main/java/net/wurstclient/mixin/TranslatableTextContentMixin.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2014-2024 Wurst-Imperium and contributors. + * + * This source code is subject to the terms of the GNU General Public + * License, version 3. If a copy of the GPL was not distributed with this + * file, You can obtain one at: https://www.gnu.org/licenses/gpl-3.0.txt + */ +package net.wurstclient.mixin; + +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; + +import net.minecraft.text.TextContent; +import net.minecraft.text.TranslatableTextContent; +import net.minecraft.util.Language; + +@Mixin(TranslatableTextContent.class) +public abstract class TranslatableTextContentMixin implements TextContent +{ + /** + * Ensures that any chat messages, written books, signs, etc. cannot resolve + * Wurst-related translation keys. + * + *

+ * Fixes at least one security vulnerability affecting Minecraft 1.20 and + * later versions, where the server can detect the presence of Wurst by + * abusing Minecraft's sign editing feature. When a player edits a sign, any + * translated text and keybind text components on that sign are resolved by + * the client and sent back to the server as plain text. This allows the + * server to detect the presence of non-vanilla translation keys. + * + *

+ * It is likely that similar vulnerabilities exist or will exist in other + * parts of the game, such as chat messages and written books. Mojang has a + * long history of failing to properly secure their text component system + * (see BookHack, OP-Sign, BookDupe). Therefore it's best to cut off this + * entire attack vector at the source. + */ + @WrapOperation(at = @At(value = "INVOKE", + target = "Lnet/minecraft/util/Language;get(Ljava/lang/String;)Ljava/lang/String;", + ordinal = 0), method = "updateTranslations()V") + private String translate(Language instance, String key, + Operation original) + { + if(key != null && key.contains("wurst")) + return key; + + return original.call(instance, key); + } + + /** + * Same as above, but for translatable text components with a fallback. + */ + @WrapOperation(at = @At(value = "INVOKE", + target = "Lnet/minecraft/util/Language;get(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;", + ordinal = 0), method = "updateTranslations()V") + private String translateWithFallback(Language instance, String key, + String fallback, Operation original) + { + if(key != null && key.contains("wurst")) + return fallback; + + return original.call(instance, key, fallback); + } +} diff --git a/src/main/java/net/wurstclient/nochatreports/ForcedChatReportsScreen.java b/src/main/java/net/wurstclient/nochatreports/ForcedChatReportsScreen.java index 34e37b67b6..15f98e3694 100644 --- a/src/main/java/net/wurstclient/nochatreports/ForcedChatReportsScreen.java +++ b/src/main/java/net/wurstclient/nochatreports/ForcedChatReportsScreen.java @@ -47,12 +47,13 @@ public final class ForcedChatReportsScreen extends Screen public ForcedChatReportsScreen(Screen prevScreen) { - super(Text.literal(ChatUtils.WURST_PREFIX).append( - Text.translatable("gui.wurst.nochatreports.unsafe_server.title"))); + super(Text.literal(ChatUtils.WURST_PREFIX) + .append(Text.literal(WurstClient.INSTANCE + .translate("gui.wurst.nochatreports.unsafe_server.title")))); this.prevScreen = prevScreen; - reason = - Text.translatable("gui.wurst.nochatreports.unsafe_server.message"); + reason = Text.literal(WurstClient.INSTANCE + .translate("gui.wurst.nochatreports.unsafe_server.message")); NoChatReportsOtf ncr = WurstClient.INSTANCE.getOtfs().noChatReportsOtf; sigButtonMsg = () -> WurstClient.INSTANCE diff --git a/src/main/java/net/wurstclient/nochatreports/NcrModRequiredScreen.java b/src/main/java/net/wurstclient/nochatreports/NcrModRequiredScreen.java index 3f376edbf4..7b0f576a81 100644 --- a/src/main/java/net/wurstclient/nochatreports/NcrModRequiredScreen.java +++ b/src/main/java/net/wurstclient/nochatreports/NcrModRequiredScreen.java @@ -43,12 +43,12 @@ public final class NcrModRequiredScreen extends Screen public NcrModRequiredScreen(Screen prevScreen) { - super(Text.literal(ChatUtils.WURST_PREFIX).append( - Text.translatable("gui.wurst.nochatreports.ncr_mod_server.title"))); + super(Text.literal(ChatUtils.WURST_PREFIX + WurstClient.INSTANCE + .translate("gui.wurst.nochatreports.ncr_mod_server.title"))); this.prevScreen = prevScreen; - reason = - Text.translatable("gui.wurst.nochatreports.ncr_mod_server.message"); + reason = Text.literal(WurstClient.INSTANCE + .translate("gui.wurst.nochatreports.ncr_mod_server.message")); OtfList otfs = WurstClient.INSTANCE.getOtfs(); diff --git a/src/main/java/net/wurstclient/other_features/NoChatReportsOtf.java b/src/main/java/net/wurstclient/other_features/NoChatReportsOtf.java index 3e81dd824a..18782ab6c3 100644 --- a/src/main/java/net/wurstclient/other_features/NoChatReportsOtf.java +++ b/src/main/java/net/wurstclient/other_features/NoChatReportsOtf.java @@ -20,7 +20,6 @@ import net.wurstclient.Category; import net.wurstclient.DontBlock; import net.wurstclient.SearchTags; -import net.wurstclient.WurstClient; import net.wurstclient.events.UpdateListener; import net.wurstclient.other_feature.OtherFeature; import net.wurstclient.settings.CheckboxSetting; @@ -81,15 +80,15 @@ private void onLoginStart(ClientLoginNetworkHandler handler, public MessageIndicator modifyIndicator(Text message, MessageSignatureData signature, MessageIndicator indicator) { - if(!WurstClient.INSTANCE.isEnabled() || MC.isInSingleplayer()) + if(!WURST.isEnabled() || MC.isInSingleplayer()) return indicator; if(indicator != null || signature == null) return indicator; return new MessageIndicator(0xE84F58, Icon.CHAT_MODIFIED, - Text.literal(ChatUtils.WURST_PREFIX + "\u00a7cReportable\u00a7r - ") - .append(Text.translatable( + Text.literal(ChatUtils.WURST_PREFIX + "\u00a7cReportable\u00a7r - " + + WURST.translate( "description.wurst.nochatreports.message_is_reportable")), "Reportable"); } @@ -102,8 +101,7 @@ public boolean isEnabled() public boolean isActive() { - return isEnabled() && WurstClient.INSTANCE.isEnabled() - && !MC.isInSingleplayer(); + return isEnabled() && WURST.isEnabled() && !MC.isInSingleplayer(); } @Override diff --git a/src/main/java/net/wurstclient/serverfinder/WurstServerPinger.java b/src/main/java/net/wurstclient/serverfinder/WurstServerPinger.java index 3382befb8e..9bcf1947af 100644 --- a/src/main/java/net/wurstclient/serverfinder/WurstServerPinger.java +++ b/src/main/java/net/wurstclient/serverfinder/WurstServerPinger.java @@ -69,11 +69,6 @@ public boolean isWorking() return !failed; } - public boolean isOtherVersion() - { - return server.protocolVersion != 47; - } - public String getServerIP() { return server.address; diff --git a/src/main/resources/wurst.accesswidener b/src/main/resources/wurst.accesswidener index 5017ea7222..9085fb6356 100644 --- a/src/main/resources/wurst.accesswidener +++ b/src/main/resources/wurst.accesswidener @@ -8,6 +8,7 @@ accessible field net/minecraft/client/MinecraftClient itemUseCooldown I accessible field net/minecraft/client/gui/hud/ChatHud visibleMessages Ljava/util/List; accessible field net/minecraft/client/gui/screen/Screen drawables Ljava/util/List; accessible field net/minecraft/client/gui/screen/ingame/CreativeInventoryScreen selectedTab Lnet/minecraft/item/ItemGroup; +accessible field net/minecraft/client/gui/screen/option/ControlsListWidget$KeyBindingEntry bindingName Lnet/minecraft/text/Text; accessible field net/minecraft/client/network/ClientPlayNetworkHandler messagePacker Lnet/minecraft/network/message/MessageChain$Packer; accessible field net/minecraft/client/network/ClientPlayNetworkHandler session Lnet/minecraft/network/encryption/ClientPlayerSession; accessible field net/minecraft/client/network/ClientPlayerEntity lastPitch F diff --git a/src/main/resources/wurst.mixins.json b/src/main/resources/wurst.mixins.json index 1b46047239..dcd42d45f4 100644 --- a/src/main/resources/wurst.mixins.json +++ b/src/main/resources/wurst.mixins.json @@ -25,6 +25,7 @@ "ClientPlayerInteractionManagerMixin", "ClientPlayNetworkHandlerMixin", "ClientWorldMixin", + "ControlsListWidgetMixin", "CreativeInventoryScreenMixin", "DeathScreenMixin", "DirectConnectScreenMixin", @@ -40,6 +41,7 @@ "IngameHudMixin", "InGameOverlayRendererMixin", "KeyBindingMixin", + "KeybindTextContentMixin", "KeyboardMixin", "LanguageManagerMixin", "LivingEntityRendererMixin", @@ -62,6 +64,7 @@ "TelemetryManagerMixin", "TextVisitFactoryMixin", "TitleScreenMixin", + "TranslatableTextContentMixin", "WorldMixin", "WorldRendererMixin" ],