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..8518f34 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.1 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/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 new file mode 100644 index 0000000..b79d78c --- /dev/null +++ b/src/main/java/me/wolfii/moddetectionpreventer/mixin/datagetter/TranslationStorageMixin.java @@ -0,0 +1,106 @@ +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.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..1d27110 100644 --- a/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java +++ b/src/main/java/me/wolfii/moddetectionpreventer/text/KeybindFilter.java @@ -1,17 +1,63 @@ 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.option.KeyBinding; +import net.minecraft.client.util.InputUtil; +import net.minecraft.util.Language; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.OptionalInt; + + +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 ! KeybindFilter.unfilteredKeybindings.containsKey(keybindingToCheck); } + + /// Gets the translated name of a key's default setting. + public static String defaultKeybindingValue(String keybindingToCheck) { + @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 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 0891e2e..0000000 Binary files a/src/main/resources/data/vanillaTranslationKeys.gzip and /dev/null differ 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..7da9113 100644 --- a/src/main/resources/moddetectionpreventer.mixins.json +++ b/src/main/resources/moddetectionpreventer.mixins.json @@ -1,14 +1,16 @@ { - "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.GameOptionsMixin", + "datagetter.TranslationStorageMixin" + ], + "injectors": { + "defaultRequire": 1 + } }