From d9b994e49b5c331d43fd2149e23b88985f6cc547 Mon Sep 17 00:00:00 2001 From: Totobird <> Date: Wed, 20 Nov 2024 19:43:24 -0500 Subject: [PATCH 1/2] 1.21. The source of translation keys are now considered before blocking them entirely, hopefully making MDP completely undetectable. Keybind requests from the server now return the factory setting value. Log messages added. --- .gitignore | 1 + build.gradle | 4 + gradle.properties | 8 +- .../ModDetectionPreventer.java | 18 +++ .../client/ModDetectionPreventerClient.java | 13 --- .../mixin/AbstractSignEditScreenMixin.java | 20 ---- .../mixin/AnvilEditScreenMixin.java | 21 ---- .../mixin/AnvilScreenHandlerMixin.java | 16 --- .../AbstractSignEditScreenMixin.java | 32 ++++++ .../antidetection/AnvilEditScreenMixin.java | 38 +++++++ .../AnvilScreenHandlerMixin.java | 27 +++++ .../datagetter/TranslationStorageMixin.java | 107 ++++++++++++++++++ .../text/CombinedFilter.java | 32 +++--- .../text/KeybindFilter.java | 70 +++++++++--- .../text/TranslationFilter.java | 74 ++++++------ .../data/vanillaTranslationKeys.gzip | Bin 35696 -> 0 bytes src/main/resources/fabric.mod.json | 13 +-- .../moddetectionpreventer.accesswidener | 3 + .../moddetectionpreventer.mixins.json | 25 ++-- 19 files changed, 365 insertions(+), 157 deletions(-) create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/ModDetectionPreventer.java delete mode 100644 src/main/java/me/wolfii/moddetectionpreventer/client/ModDetectionPreventerClient.java delete mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/AbstractSignEditScreenMixin.java delete mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilEditScreenMixin.java delete mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilScreenHandlerMixin.java create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AbstractSignEditScreenMixin.java create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilEditScreenMixin.java create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilScreenHandlerMixin.java create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java delete mode 100644 src/main/resources/data/vanillaTranslationKeys.gzip create mode 100644 src/main/resources/moddetectionpreventer.accesswidener diff --git a/.gitignore b/.gitignore index 186e48f..189c2b1 100644 --- a/.gitignore +++ b/.gitignore @@ -103,6 +103,7 @@ $RECYCLE.BIN/ .gradle build/ +remappedSrc # Ignore Gradle GUI config gradle-app.setting diff --git a/build.gradle b/build.gradle index e6644ae..23ebb3a 100644 --- a/build.gradle +++ b/build.gradle @@ -21,6 +21,10 @@ processResources { } } +loom { + accessWidenerPath = file("src/main/resources/moddetectionpreventer.accesswidener"); +} + def targetJavaVersion = 17 tasks.withType(JavaCompile).configureEach { it.options.encoding = "UTF-8" diff --git a/gradle.properties b/gradle.properties index ac1c523..bea359e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,8 +1,8 @@ org.gradle.jvmargs=-Xmx1G -minecraft_version=1.20.4 -yarn_mappings=1.20.4+build.3 -loader_version=0.15.7 +minecraft_version=1.21.1 +yarn_mappings=1.21.1+build.3 +loader_version=0.16.9 -mod_version = 1.2.0 +mod_version = 2.0.0 archives_base_name = ModDetectionPreventer \ No newline at end of file diff --git a/src/main/java/me/wolfii/moddetectionpreventer/ModDetectionPreventer.java b/src/main/java/me/wolfii/moddetectionpreventer/ModDetectionPreventer.java new file mode 100644 index 0000000..5f6c5b0 --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/ModDetectionPreventer.java @@ -0,0 +1,18 @@ +package me.wolfii.moddetectionpreventer; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + + +public final class ModDetectionPreventer { + private ModDetectionPreventer() { throw new UnsupportedOperationException("Consider this class abstract-final"); } + + public static Logger LOGGER = LoggerFactory.getLogger("ModDetectionPreventer"); + + + /// Called when the server tries to abuse the exploit. + public static void warnAccess(String action, String kind, String key) { + LOGGER.warn("{} incoming request for {} `{}`", action, kind, key); + } + +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/client/ModDetectionPreventerClient.java b/src/main/java/me/wolfii/moddetectionpreventer/client/ModDetectionPreventerClient.java deleted file mode 100644 index 09b6d14..0000000 --- a/src/main/java/me/wolfii/moddetectionpreventer/client/ModDetectionPreventerClient.java +++ /dev/null @@ -1,13 +0,0 @@ -package me.wolfii.moddetectionpreventer.client; - -import me.wolfii.moddetectionpreventer.text.TranslationFilter; -import net.fabricmc.api.ClientModInitializer; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class ModDetectionPreventerClient implements ClientModInitializer { - public static Logger LOGGER = LoggerFactory.getLogger("ModDetectionPreventer"); - public void onInitializeClient() { - TranslationFilter.loadTranslations(); - } -} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AbstractSignEditScreenMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/AbstractSignEditScreenMixin.java deleted file mode 100644 index de9c203..0000000 --- a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AbstractSignEditScreenMixin.java +++ /dev/null @@ -1,20 +0,0 @@ -package me.wolfii.moddetectionpreventer.mixin; - -import me.wolfii.moddetectionpreventer.text.CombinedFilter; -import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; -import net.minecraft.text.*; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -import java.util.function.Function; -import java.util.stream.Stream; - -@Mixin(AbstractSignEditScreen.class) -public class AbstractSignEditScreenMixin { - - @Redirect(method = "(Lnet/minecraft/block/entity/SignBlockEntity;ZZLnet/minecraft/text/Text;)V", at = @At(value = "INVOKE", target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;")) - private Stream preventSignModDetection(Stream instance, Function function) { - return instance.map(message -> CombinedFilter.filterKeybindsRecursive(message).getString()); - } -} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilEditScreenMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilEditScreenMixin.java deleted file mode 100644 index 117b79d..0000000 --- a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilEditScreenMixin.java +++ /dev/null @@ -1,21 +0,0 @@ -package me.wolfii.moddetectionpreventer.mixin; - -import me.wolfii.moddetectionpreventer.text.CombinedFilter; -import net.minecraft.client.gui.screen.ingame.AnvilScreen; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(AnvilScreen.class) -public class AnvilEditScreenMixin { - @Redirect(method = "onSlotUpdate", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;")) - private String preventAnvilItemNameChangeModDetection(Text instance) { - return CombinedFilter.filterKeybindsRecursive(instance).getString(); - } - - @Redirect(method = "onRenamed", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;")) - private String preventAnvilItemNameCheckModDetection(Text instance) { - return CombinedFilter.filterKeybindsRecursive(instance).getString(); - } -} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilScreenHandlerMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilScreenHandlerMixin.java deleted file mode 100644 index 4185765..0000000 --- a/src/main/java/me/wolfii/moddetectionpreventer/mixin/AnvilScreenHandlerMixin.java +++ /dev/null @@ -1,16 +0,0 @@ -package me.wolfii.moddetectionpreventer.mixin; - -import me.wolfii.moddetectionpreventer.text.CombinedFilter; -import net.minecraft.screen.AnvilScreenHandler; -import net.minecraft.text.Text; -import org.spongepowered.asm.mixin.Mixin; -import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; - -@Mixin(AnvilScreenHandler.class) -public class AnvilScreenHandlerMixin { - @Redirect(method = "updateResult", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;")) - private String preventAnvilModDetection(Text instance) { - return CombinedFilter.filterKeybindsRecursive(instance).getString(); - } -} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AbstractSignEditScreenMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AbstractSignEditScreenMixin.java new file mode 100644 index 0000000..bea0550 --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AbstractSignEditScreenMixin.java @@ -0,0 +1,32 @@ +package me.wolfii.moddetectionpreventer.mixin.antidetection; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import me.wolfii.moddetectionpreventer.text.CombinedFilter; +import net.minecraft.client.gui.screen.ingame.AbstractSignEditScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + +import java.util.function.Function; +import java.util.stream.Stream; + + +/// Catches incoming sign screens, filtering out components. +@Mixin(AbstractSignEditScreen.class) +class AbstractSignEditScreenMixin { + + @WrapOperation( + method = "(Lnet/minecraft/block/entity/SignBlockEntity;ZZLnet/minecraft/text/Text;)V", + at = @At( + value = "INVOKE", + target = "Ljava/util/stream/Stream;map(Ljava/util/function/Function;)Ljava/util/stream/Stream;" + ) + ) + private Stream preventSignModDetection(Stream instance, Function function, Operation> original) { + return original.call( + instance.map(CombinedFilter::filterComponents), + function + ); + } +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilEditScreenMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilEditScreenMixin.java new file mode 100644 index 0000000..4beece7 --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilEditScreenMixin.java @@ -0,0 +1,38 @@ +package me.wolfii.moddetectionpreventer.mixin.antidetection; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import me.wolfii.moddetectionpreventer.text.CombinedFilter; +import net.minecraft.client.gui.screen.ingame.AnvilScreen; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + + +/// Catches incoming anvil screens, filtering out components. +@Mixin(AnvilScreen.class) +class AnvilEditScreenMixin { + + @WrapOperation( + method = "onSlotUpdate", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;" + ) + ) + private String preventAnvilItemNameChangeModDetection(Text instance, Operation original) { + return original.call(CombinedFilter.filterComponents(instance)); + } + + @WrapOperation( + method = "onRenamed", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;" + ) + ) + private String preventAnvilItemNameCheckModDetection(Text instance, Operation original) { + return original.call(CombinedFilter.filterComponents(instance)); + } + +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilScreenHandlerMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilScreenHandlerMixin.java new file mode 100644 index 0000000..ea440ab --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/antidetection/AnvilScreenHandlerMixin.java @@ -0,0 +1,27 @@ +package me.wolfii.moddetectionpreventer.mixin.antidetection; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import me.wolfii.moddetectionpreventer.text.CombinedFilter; +import net.minecraft.screen.AnvilScreenHandler; +import net.minecraft.text.Text; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; + + +/// Catches incoming anvil screens, filtering out components. +@Mixin(AnvilScreenHandler.class) +class AnvilScreenHandlerMixin { + + @WrapOperation( + method = "updateResult", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/text/Text;getString()Ljava/lang/String;" + ) + ) + private String preventAnvilModDetection(Text instance, Operation original) { + return original.call(CombinedFilter.filterComponents(instance)); + } + +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java new file mode 100644 index 0000000..52f2ec1 --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java @@ -0,0 +1,107 @@ +package me.wolfii.moddetectionpreventer.mixin.datagetter; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import com.llamalad7.mixinextras.sugar.Local; +import me.wolfii.moddetectionpreventer.ModDetectionPreventer; +import me.wolfii.moddetectionpreventer.text.TranslationFilter; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.resource.language.TranslationStorage; +import net.minecraft.resource.*; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; + +import java.io.InputStream; +import java.nio.file.Path; +import java.util.HashMap; +import java.util.List; +import java.util.function.BiConsumer; + + +/// This mixin is used to figure out what translation keys should be filtered. +/// See `TranslationFilter` for more information. +@Mixin(TranslationStorage.class) +class TranslationStorageMixin { + + @Unique + private static HashMap nextUnfilteredTranslationKeys = new HashMap<>(); + + + + /// When the language starts loading, reset the list of translation keys to pass through. + @Inject( + method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", + at = @At( + value = "HEAD" + ) + ) + private static void clearAllowedKeys( + ResourceManager resourceManager, + List definitions, + boolean rightToLeft, + CallbackInfoReturnable cir + ) { + TranslationStorageMixin.nextUnfilteredTranslationKeys = new HashMap<>(); + } + + + /// For each translation json file, check the resource pack source. + /// + /// Translation keys defined by the vanilla pack, or by resource packs sent from the server/world, bypass the filter. + /// This (should) makes it impossible for a server to see if keys are stripped from signs/anvils, which is how they used to check if this mod is installed. + @WrapOperation( + method = "load(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V", + at = @At( + value = "INVOKE", + target = "Lnet/minecraft/util/Language;load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V" + ) + ) + private static void checkPack(InputStream inputStream, BiConsumer entryConsumer, Operation original, @Local Resource resource) { + ResourcePack pack = resource.getPack(); + boolean shouldUnfilter = false; + + // Vanilla and realms packs. + if (pack instanceof DefaultResourcePack defaultPack) { + shouldUnfilter = true; + } + + // Server resource packs. + if (pack instanceof ZipResourcePack zipPack) { + Path path = MinecraftClient.getInstance().runDirectory.toPath().relativize(zipPack.zipFile.file.toPath()); + if (path.getName(0).toString().equals(TranslationFilter.SERVER_PACK_PATH)) { + shouldUnfilter = true; + } + } + + if (shouldUnfilter) { + original.call(inputStream, (BiConsumer) (translationKey, localisedValue) -> { + TranslationStorageMixin.nextUnfilteredTranslationKeys.put(translationKey, localisedValue); + entryConsumer.accept(translationKey, localisedValue); + }); + } else { + original.call(inputStream, entryConsumer); + } + } + + + /// When all translation json files finish loading, swap the main filter to the new allowed translation keys list. + @Inject( + method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", + at = @At( + value = "RETURN" + ) + ) + private static void swapAllowedKeys( + ResourceManager resourceManager, + List definitions, + boolean rightToLeft, + CallbackInfoReturnable cir + ) { + TranslationFilter.unfilteredTranslationKeys = TranslationStorageMixin.nextUnfilteredTranslationKeys; + } + + +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/text/CombinedFilter.java b/src/main/java/me/wolfii/moddetectionpreventer/text/CombinedFilter.java index e8dca5d..02e288e 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/text/CombinedFilter.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/text/CombinedFilter.java @@ -2,29 +2,35 @@ import net.minecraft.text.*; -public class CombinedFilter { - public static Text filterKeybindsRecursive(Text message) { + +public final class CombinedFilter { + private CombinedFilter() { throw new UnsupportedOperationException("Consider this class abstract-final"); } + + + /// Recursively walks through the components of a `Text`, masking any which should be filtered. + public static Text filterComponents(Text message) { MutableText filtered = MutableText.of(message.getContent()); + + // Filter keybind components. if (message.getContent() instanceof KeybindTextContent keybindTextContent) { String keybind = keybindTextContent.getKey(); - if (KeybindFilter.isVanillaKeybinding(keybind)) { - filtered = MutableText.of(keybindTextContent); - } else { - filtered = MutableText.of(new PlainTextContent.Literal(keybind)); - } + // Send back Minecraft's factory setting value of the keybind. + filtered = MutableText.of(new PlainTextContent.Literal(KeybindFilter.defaultKeybindingValue(keybind))); } + + // Filter translatable components. if (message.getContent() instanceof TranslatableTextContent translatableTextContent) { String translationKey = translatableTextContent.getKey(); - if (TranslationFilter.isVanillaTranslation(translationKey)) { - filtered = MutableText.of(translatableTextContent); - } else { - filtered = MutableText.of(new PlainTextContent.Literal(translationKey)); - } + filtered = MutableText.of(new PlainTextContent.Literal(TranslationFilter.localisedTranslationKey(translationKey))); } + filtered.setStyle(message.getStyle()); for (Text sibling : message.getSiblings()) { - filtered.append(filterKeybindsRecursive(sibling)); + filtered.append(CombinedFilter.filterComponents(sibling)); } + return filtered; } + + } diff --git a/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java b/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java index 37bb916..683c783 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java @@ -1,17 +1,61 @@ package me.wolfii.moddetectionpreventer.text; -import java.util.List; - -public class KeybindFilter { - private static final List vanillaKeybindings = List.of("key.sneak", "key.sprint", "key.forward", - "key.left", "key.back", "key.right", "key.jump", "key.sneak", "key.sprint", "key.inventory", - "key.swapOffhand", "key.drop", "key.use", "key.attack", "key.pickItem", "key.chat", "key.playerlist", - "key.command", "key.socialInteractions", "key.screenshot", "key.togglePerspective", "key.smoothCamera", - "key.fullscreen", "key.spectatorOutlines", "key.advancements", "key.hotbar.1", "key.hotbar.2", - "key.hotbar.3", "key.hotbar.4", "key.hotbar.5", "key.hotbar.6", "key.hotbar.7", "key.hotbar.8", - "key.hotbar.9", "key.saveToolbarActivator", "key.loadToolbarActivator"); - - public static boolean isVanillaKeybinding(String keyBinding) { - return vanillaKeybindings.contains(keyBinding); +import me.wolfii.moddetectionpreventer.ModDetectionPreventer; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Language; + +import java.util.Arrays; +import java.util.OptionalInt; + + +public final class KeybindFilter { + private KeybindFilter() { throw new UnsupportedOperationException("Consider this class abstract-final"); } + + + /// Returns `true` if the keybind is a vanilla keybind. + public static boolean shouldFilterKeybinding(String keybindingToCheck) { // This is currently unused. It may be useful later on. + return Arrays.stream(MinecraftClient.getInstance().options.allKeys).noneMatch( + key -> keybindingToCheck.equals(key.getTranslationKey()) // This checks if vanilla contains the keybinding to check. + ); } + + /// Gets the translated name of a key's default setting. + public static String defaultKeybindingValue(String keybindingToCheck) { + for (KeyBinding key : MinecraftClient.getInstance().options.allKeys) { + if (keybindingToCheck.equals(key.getTranslationKey())) { + Language language = Language.getInstance(); + InputUtil.Key defaultKey = key.getDefaultKey(); + String translation = defaultKey.getTranslationKey(); + // If the keybind key has a translation, use that. + if (language.hasTranslation(translation)) { + KeybindFilter.warnAccess("Falsified", keybindingToCheck); + return language.get(translation); + } + // If the default keybind is not a control character, use that. + OptionalInt keyCodeInt = defaultKey.toInt(); + if (keyCodeInt.isPresent()) { + char keyCode = (char) keyCodeInt.getAsInt(); + if (! Character.isISOControl(keyCode)) { + KeybindFilter.warnAccess("Falsified", keybindingToCheck); + return Character.toString(keyCode); + } + } + // Otherwise just use the final part of the translation key. + String[] parts = translation.split("\\."); + KeybindFilter.warnAccess("Falsified", keybindingToCheck); + return parts[parts.length - 1].toUpperCase(); + } + } + // None of the vanilla keybindings match. Return the translation. + return TranslationFilter.localisedTranslationKey(keybindingToCheck); + } + + /// Called when the server tries to access the client's keybindings. + public static void warnAccess(String action, String key) { + ModDetectionPreventer.warnAccess(action, "keybinding", key); + } + + } diff --git a/src/main/java/me/wolfii/moddetectionpreventer/text/TranslationFilter.java b/src/main/java/me/wolfii/moddetectionpreventer/text/TranslationFilter.java index 3cff5c2..3d64e4c 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/text/TranslationFilter.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/text/TranslationFilter.java @@ -1,46 +1,46 @@ package me.wolfii.moddetectionpreventer.text; -import me.wolfii.moddetectionpreventer.client.ModDetectionPreventerClient; -import net.fabricmc.loader.api.FabricLoader; - -import java.io.*; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.*; -import java.util.zip.GZIPInputStream; - -public class TranslationFilter { - private static final Map> vanillaTranslations = new HashMap<>(); - private static final String fileLocation = "data" + File.separator + "vanillaTranslationKeys.gzip"; - - public static void loadTranslations() { - FabricLoader.getInstance().getModContainer("moddetectionpreventer").ifPresentOrElse( - modContainer -> modContainer.findPath(fileLocation).ifPresentOrElse(TranslationFilter::readTranslationsFile, - () -> ModDetectionPreventerClient.LOGGER.warn("Could not read translations file. Disabling all translations on signs.")), - () -> ModDetectionPreventerClient.LOGGER.warn("Could not get modContainer. Disabling all translations on signs.")); +import me.wolfii.moddetectionpreventer.ModDetectionPreventer; + +import java.util.HashMap; + + +public final class TranslationFilter { + private TranslationFilter() { throw new UnsupportedOperationException("Consider this class abstract-final"); } + + + /// Translation keys defined in the `vanilla` and `realms` resource packs will be passed through. + public static String VANILLA_PACK_NAME = "vanilla"; + + /// A regular expression which detects if a resource pack id is a server resource pack. + public static String SERVER_PACK_PATH = "server-resource-packs"; + + /// A list of translation keys defined by the namespaces listed in `UNFILTERED_TRANSLATION_KEY_NAMESPACES`. + /// See `TranslationStorageMixin` for more information. + public static HashMap unfilteredTranslationKeys = new HashMap<>(); + + + + /// Returns `true` if the translation key is vanilla, realms, or provided by the server/world. + public static boolean shouldFilterTranslationKey(String translationKeyToCheck) { + return ! TranslationFilter.unfilteredTranslationKeys.containsKey(translationKeyToCheck); } - private static void readTranslationsFile(Path translationsPath) { - try { - InputStream inputStream = Files.newInputStream(translationsPath); - GZIPInputStream gzipInputStream = new GZIPInputStream(new BufferedInputStream(inputStream)); - BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(gzipInputStream)); - String line; - while ((line = bufferedReader.readLine()) != null) { - String[] parts = line.split("\\.", 2); - Set category = vanillaTranslations.getOrDefault(parts[0], new HashSet<>()); - if (parts.length > 1) category.add(parts[1]); - vanillaTranslations.put(parts[0], category); - } - } catch (Exception e) { - ModDetectionPreventerClient.LOGGER.warn("There has been an error loading the vanilla translations. Disabling all translations on signs."); - ModDetectionPreventerClient.LOGGER.warn(e.getMessage(), e); + /// Get the localised value of a translation key. + public static String localisedTranslationKey(String translationKey) { + if (TranslationFilter.shouldFilterTranslationKey(translationKey)) { + TranslationFilter.warnAccess("Blocked", translationKey); + return translationKey; + } else { + TranslationFilter.warnAccess("Allowed", translationKey); + return TranslationFilter.unfilteredTranslationKeys.get(translationKey); // Guaranteed to exist. } } - public static boolean isVanillaTranslation(String translationKey) { - String[] parts = translationKey.split("\\.", 2); - if (parts.length == 1) return vanillaTranslations.containsKey(parts[0]); - return vanillaTranslations.getOrDefault(parts[0], Set.of()).contains(parts[1]); + /// Called when the server tries to access the client's keybindings. + public static void warnAccess(String action, String key) { + ModDetectionPreventer.warnAccess(action, "translation key", key); } + + } diff --git a/src/main/resources/data/vanillaTranslationKeys.gzip b/src/main/resources/data/vanillaTranslationKeys.gzip deleted file mode 100644 index 0891e2edb9eb9fd60c11ef1bd0f3ff8b8b73575c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35696 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 PSnU4+n(Tt}9$NzdZEw4! diff --git a/src/main/resources/fabric.mod.json b/src/main/resources/fabric.mod.json index c4e0dd2..86f3270 100644 --- a/src/main/resources/fabric.mod.json +++ b/src/main/resources/fabric.mod.json @@ -8,7 +8,8 @@ "JustAlittleWolf" ], "contributors": [ - "croaak" + "croaak", + "Totobird-Creations" ], "contact": { "sources": "https://github.com/JustAlittleWolf/ModDetectionPreventer", @@ -17,16 +18,12 @@ "license": "MIT", "icon": "assets/moddetectionpreventer/icon.png", "environment": "client", - "entrypoints": { - "client": [ - "me.wolfii.moddetectionpreventer.client.ModDetectionPreventerClient" - ] - }, "mixins": [ "moddetectionpreventer.mixins.json" ], + "accessWidener": "moddetectionpreventer.accesswidener", "depends": { - "fabricloader": ">=0.15.0", - "minecraft": ">=1.20.3 <=1.20.4" + "fabricloader": ">=0.16.9", + "minecraft": "~1.21" } } diff --git a/src/main/resources/moddetectionpreventer.accesswidener b/src/main/resources/moddetectionpreventer.accesswidener new file mode 100644 index 0000000..4c63025 --- /dev/null +++ b/src/main/resources/moddetectionpreventer.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named +accessible field net/minecraft/resource/ZipResourcePack zipFile Lnet/minecraft/resource/ZipResourcePack$ZipFileWrapper; +accessible field net/minecraft/resource/ZipResourcePack$ZipFileWrapper file Ljava/io/File; diff --git a/src/main/resources/moddetectionpreventer.mixins.json b/src/main/resources/moddetectionpreventer.mixins.json index c49aa1c..5bb8330 100644 --- a/src/main/resources/moddetectionpreventer.mixins.json +++ b/src/main/resources/moddetectionpreventer.mixins.json @@ -1,14 +1,15 @@ { - "required": true, - "minVersion": "0.8", - "package": "me.wolfii.moddetectionpreventer.mixin", - "compatibilityLevel": "JAVA_17", - "client": [ - "AbstractSignEditScreenMixin", - "AnvilEditScreenMixin", - "AnvilScreenHandlerMixin" - ], - "injectors": { - "defaultRequire": 1 - } + "required": true, + "minVersion": "0.8", + "package": "me.wolfii.moddetectionpreventer.mixin", + "compatibilityLevel": "JAVA_17", + "client": [ + "antidetection.AbstractSignEditScreenMixin", + "antidetection.AnvilEditScreenMixin", + "antidetection.AnvilScreenHandlerMixin", + "datagetter.TranslationStorageMixin" + ], + "injectors": { + "defaultRequire": 1 + } } From 1925149425d88b1e7757bad909a849f4520e1cce Mon Sep 17 00:00:00 2001 From: Totobird <> Date: Fri, 22 Nov 2024 21:25:50 -0500 Subject: [PATCH 2/2] Fixed a bug which caused keybinds from some mods to be classified as 'vanilla' --- gradle.properties | 2 +- .../mixin/datagetter/GameOptionsMixin.java | 79 +++++++++++++++++++ .../datagetter/TranslationStorageMixin.java | 1 - .../text/KeybindFilter.java | 54 +++++++------ .../moddetectionpreventer.mixins.json | 1 + 5 files changed, 109 insertions(+), 28 deletions(-) create mode 100644 src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/GameOptionsMixin.java diff --git a/gradle.properties b/gradle.properties index bea359e..8518f34 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,5 +4,5 @@ minecraft_version=1.21.1 yarn_mappings=1.21.1+build.3 loader_version=0.16.9 -mod_version = 2.0.0 +mod_version = 2.0.1 archives_base_name = ModDetectionPreventer \ No newline at end of file diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/GameOptionsMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/GameOptionsMixin.java new file mode 100644 index 0000000..9745cc1 --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/GameOptionsMixin.java @@ -0,0 +1,79 @@ +package me.wolfii.moddetectionpreventer.mixin.datagetter; + +import com.llamalad7.mixinextras.injector.wrapoperation.Operation; +import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; +import me.wolfii.moddetectionpreventer.ModDetectionPreventer; +import me.wolfii.moddetectionpreventer.text.KeybindFilter; +import net.minecraft.client.MinecraftClient; +import net.minecraft.client.option.GameOptions; +import net.minecraft.client.option.KeyBinding; +import net.minecraft.client.option.StickyKeyBinding; +import net.minecraft.client.util.InputUtil; +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; + +import java.io.File; +import java.util.function.BooleanSupplier; + + +@Mixin(GameOptions.class) +class GameOptionsMixin { + + /// When the vanilla keybindings start loading, reset the list of keybindings to pass through. + @Inject( + method = "", + at = @At( + value = "INVOKE", + target = "Ljava/lang/Object;()V", + shift = At.Shift.AFTER + ) + ) + private void resetAllowedKeys(MinecraftClient client, File optionsFile, CallbackInfo ci) { + KeybindFilter.unfilteredKeybindings.clear(); + } + + /// When the vanilla keybindings are initialised, keep a record of them. + @WrapOperation( + method = "", + at = @At( + value = "NEW", + target = "(Ljava/lang/String;Lnet/minecraft/client/util/InputUtil$Type;ILjava/lang/String;)Lnet/minecraft/client/option/KeyBinding;" + ) + ) + private KeyBinding getAllowedKeys0(String translationKey, InputUtil.Type type, int code, String category, Operation original) { + KeyBinding keybinding = original.call(translationKey, type, code, category); + KeybindFilter.unfilteredKeybindings.put(translationKey, keybinding); + return keybinding; + } + + /// When the vanilla keybindings are initialised, keep a record of them. + @WrapOperation( + method = "", + at = @At( + value = "NEW", + target = "(Ljava/lang/String;ILjava/lang/String;)Lnet/minecraft/client/option/KeyBinding;" + ) + ) + private KeyBinding getAllowedKeys1(String translationKey, int code, String category, Operation original) { + KeyBinding keybinding = original.call(translationKey, code, category); + KeybindFilter.unfilteredKeybindings.put(translationKey, keybinding); + return keybinding; + } + + /// When the vanilla keybindings are initialised, keep a record of them. + @WrapOperation( + method = "", + at = @At( + value = "NEW", + target = "(Ljava/lang/String;ILjava/lang/String;Ljava/util/function/BooleanSupplier;)Lnet/minecraft/client/option/StickyKeyBinding;" + ) + ) + private StickyKeyBinding getAllowedKeys2(String translationKey, int code, String category, BooleanSupplier toggleGetter, Operation original) { + StickyKeyBinding keybinding = original.call(translationKey, code, category, toggleGetter); + KeybindFilter.unfilteredKeybindings.put(translationKey, keybinding); + return keybinding; + } + +} diff --git a/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java index 52f2ec1..b79d78c 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java @@ -3,7 +3,6 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; -import me.wolfii.moddetectionpreventer.ModDetectionPreventer; import me.wolfii.moddetectionpreventer.text.TranslationFilter; import net.minecraft.client.MinecraftClient; import net.minecraft.client.resource.language.TranslationStorage; diff --git a/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java b/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java index 683c783..1d27110 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java @@ -1,12 +1,12 @@ package me.wolfii.moddetectionpreventer.text; import me.wolfii.moddetectionpreventer.ModDetectionPreventer; -import net.minecraft.client.MinecraftClient; import net.minecraft.client.option.KeyBinding; import net.minecraft.client.util.InputUtil; import net.minecraft.util.Language; +import org.jetbrains.annotations.Nullable; -import java.util.Arrays; +import java.util.HashMap; import java.util.OptionalInt; @@ -14,39 +14,41 @@ public final class KeybindFilter { private KeybindFilter() { throw new UnsupportedOperationException("Consider this class abstract-final"); } + /// A list of keybindings defined by the vanilla game, along with their default keycodes. + /// See `GameOptionsMixin` for more information. + public static HashMap unfilteredKeybindings = new HashMap<>(); + + /// Returns `true` if the keybind is a vanilla keybind. public static boolean shouldFilterKeybinding(String keybindingToCheck) { // This is currently unused. It may be useful later on. - return Arrays.stream(MinecraftClient.getInstance().options.allKeys).noneMatch( - key -> keybindingToCheck.equals(key.getTranslationKey()) // This checks if vanilla contains the keybinding to check. - ); + return ! KeybindFilter.unfilteredKeybindings.containsKey(keybindingToCheck); } /// Gets the translated name of a key's default setting. public static String defaultKeybindingValue(String keybindingToCheck) { - for (KeyBinding key : MinecraftClient.getInstance().options.allKeys) { - if (keybindingToCheck.equals(key.getTranslationKey())) { - Language language = Language.getInstance(); - InputUtil.Key defaultKey = key.getDefaultKey(); - String translation = defaultKey.getTranslationKey(); - // If the keybind key has a translation, use that. - if (language.hasTranslation(translation)) { + @Nullable KeyBinding defaultKeybinding = KeybindFilter.unfilteredKeybindings.get(keybindingToCheck); + if (defaultKeybinding != null) { + InputUtil.Key defaultKey = defaultKeybinding.getDefaultKey(); + String translation = defaultKey.getTranslationKey(); + Language language = Language.getInstance(); + // If the keybind key has a translation, use that. + if (language.hasTranslation(translation)) { + KeybindFilter.warnAccess("Falsified", keybindingToCheck); + return language.get(keybindingToCheck); + } + // If the default keybind is not a control character, use that. + OptionalInt keyCodeInt = defaultKey.toInt(); + if (keyCodeInt.isPresent()) { + char keyCode = (char) keyCodeInt.getAsInt(); + if (! Character.isISOControl(keyCode)) { KeybindFilter.warnAccess("Falsified", keybindingToCheck); - return language.get(translation); - } - // If the default keybind is not a control character, use that. - OptionalInt keyCodeInt = defaultKey.toInt(); - if (keyCodeInt.isPresent()) { - char keyCode = (char) keyCodeInt.getAsInt(); - if (! Character.isISOControl(keyCode)) { - KeybindFilter.warnAccess("Falsified", keybindingToCheck); - return Character.toString(keyCode); - } + return Character.toString(keyCode); } - // Otherwise just use the final part of the translation key. - String[] parts = translation.split("\\."); - KeybindFilter.warnAccess("Falsified", keybindingToCheck); - return parts[parts.length - 1].toUpperCase(); } + // Otherwise just use the final part of the translation key. + String[] parts = translation.split("\\."); + KeybindFilter.warnAccess("Falsified", keybindingToCheck); + return parts[parts.length - 1].toUpperCase(); } // None of the vanilla keybindings match. Return the translation. return TranslationFilter.localisedTranslationKey(keybindingToCheck); diff --git a/src/main/resources/moddetectionpreventer.mixins.json b/src/main/resources/moddetectionpreventer.mixins.json index 5bb8330..7da9113 100644 --- a/src/main/resources/moddetectionpreventer.mixins.json +++ b/src/main/resources/moddetectionpreventer.mixins.json @@ -7,6 +7,7 @@ "antidetection.AbstractSignEditScreenMixin", "antidetection.AnvilEditScreenMixin", "antidetection.AnvilScreenHandlerMixin", + "datagetter.GameOptionsMixin", "datagetter.TranslationStorageMixin" ], "injectors": {