mirror of
https://github.com/ViaVersion/ViaFabricPlus.git
synced 2024-12-25 17:18:04 +01:00
This commit is contained in:
parent
36c3a2cd53
commit
32bb6c00e5
@ -30,6 +30,7 @@ import de.florianmichael.viafabricplus.definition.classic.protocol.CustomClassic
|
||||
import de.florianmichael.viafabricplus.definition.FontCacheFix;
|
||||
import de.florianmichael.viafabricplus.definition.ArmorPointCalculation;
|
||||
import de.florianmichael.viafabricplus.information.InformationSystem;
|
||||
import de.florianmichael.viafabricplus.mappings.CharacterMappings;
|
||||
import de.florianmichael.viafabricplus.mappings.ItemReleaseVersionMappings;
|
||||
import de.florianmichael.viafabricplus.mappings.PackFormatsMappings;
|
||||
import de.florianmichael.viafabricplus.protocolhack.ProtocolHack;
|
||||
@ -76,6 +77,7 @@ public class ViaFabricPlus {
|
||||
|
||||
// Fixes which requires to be loaded pre
|
||||
ChatLengthCalculation.create();
|
||||
CharacterMappings.load();
|
||||
FontCacheFix.init();
|
||||
|
||||
// Protocol Translator
|
||||
|
@ -55,29 +55,11 @@ public class FontCacheFix {
|
||||
|
||||
public enum BuiltinEmptyGlyph1_12_2 implements Glyph {
|
||||
|
||||
VERY_MISSING(() -> BuiltinEmptyGlyph1_12_2.createRectImage((x, y) -> x == 0 || x + 1 == 5 || y == 0 || y + 1 == 8 ? -1 : 0));
|
||||
|
||||
final NativeImage image;
|
||||
|
||||
BuiltinEmptyGlyph1_12_2(Supplier<NativeImage> imageSupplier) {
|
||||
this.image = imageSupplier.get();
|
||||
}
|
||||
|
||||
private static NativeImage createRectImage(BuiltinEmptyGlyph1_12_2.ColorSupplier colorSupplier) {
|
||||
final NativeImage nativeImage = new NativeImage(NativeImage.Format.RGBA, 5, 8, false);
|
||||
|
||||
for (int y = 0; y < 8; ++y) {
|
||||
for (int x = 0; x < 5; ++x) {
|
||||
nativeImage.setColor(x, y, colorSupplier.getColor(x, y));
|
||||
}
|
||||
}
|
||||
nativeImage.untrack();
|
||||
return nativeImage;
|
||||
}
|
||||
VERY_MISSING;
|
||||
|
||||
@Override
|
||||
public float getAdvance() {
|
||||
return this.image.getWidth() + 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -86,12 +68,12 @@ public class FontCacheFix {
|
||||
|
||||
@Override
|
||||
public int getWidth() {
|
||||
return BuiltinEmptyGlyph1_12_2.this.image.getWidth();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getHeight() {
|
||||
return BuiltinEmptyGlyph1_12_2.this.image.getHeight();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -101,7 +83,6 @@ public class FontCacheFix {
|
||||
|
||||
@Override
|
||||
public void upload(int x, int y) {
|
||||
BuiltinEmptyGlyph1_12_2.this.image.upload(0, x, y, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -110,11 +91,5 @@ public class FontCacheFix {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
interface ColorSupplier {
|
||||
|
||||
int getColor(int x, int y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,115 @@
|
||||
/*
|
||||
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
|
||||
* Copyright (C) 2021-2023 FlorianMichael/EnZaXD and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package de.florianmichael.viafabricplus.injection.mixin.fixes.minecraft;
|
||||
|
||||
import de.florianmichael.viafabricplus.definition.FontCacheFix;
|
||||
import de.florianmichael.viafabricplus.injection.access.IFontStorage;
|
||||
import de.florianmichael.viafabricplus.mappings.CharacterMappings;
|
||||
import de.florianmichael.viafabricplus.protocolhack.ProtocolHack;
|
||||
import net.minecraft.client.font.*;
|
||||
import net.minecraft.client.font.Font;
|
||||
import net.minecraft.util.Identifier;
|
||||
import net.raphimc.vialoader.util.VersionEnum;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
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.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
import org.spongepowered.asm.mixin.injection.callback.LocalCapture;
|
||||
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@Mixin(FontStorage.class)
|
||||
public abstract class MixinFontStorage implements IFontStorage {
|
||||
|
||||
@Shadow protected abstract GlyphRenderer getGlyphRenderer(RenderableGlyph c);
|
||||
|
||||
@Shadow @Final private GlyphContainer<GlyphRenderer> glyphRendererCache;
|
||||
@Shadow @Final private GlyphContainer<FontStorage.GlyphPair> glyphCache;
|
||||
@Shadow private GlyphRenderer blankGlyphRenderer;
|
||||
|
||||
@Shadow @Final private Identifier id;
|
||||
|
||||
@Unique
|
||||
private Map<String, List<Integer>> forbiddenCharacters;
|
||||
|
||||
@Unique
|
||||
private boolean obfuscation;
|
||||
|
||||
@Inject(method = "setFonts", at = @At("HEAD"))
|
||||
public void trackForbiddenCharacters(List<Font> fonts, CallbackInfo ci) {
|
||||
forbiddenCharacters = CharacterMappings.getForbiddenCharactersForID(this.id);
|
||||
}
|
||||
|
||||
@Unique
|
||||
private boolean isForbiddenCharacter(final Font font, final int codePoint) {
|
||||
try {
|
||||
return forbiddenCharacters.get(font.getClass().getSimpleName()).contains(codePoint);
|
||||
} catch (Exception ignored) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "findGlyph", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/Font;getGlyph(I)Lnet/minecraft/client/font/Glyph;"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
|
||||
public void injectFindGlyph(int codePoint, CallbackInfoReturnable<FontStorage.GlyphPair> cir, Glyph glyph, Iterator var3, Font font) {
|
||||
if (ProtocolHack.getTargetVersion().isOlderThanOrEqualTo(VersionEnum.r1_19_4) && this.id.getNamespace().equals("minecraft")) {
|
||||
if (isForbiddenCharacter(font, codePoint)) cir.setReturnValue(FontStorage.GlyphPair.MISSING);
|
||||
|
||||
if (FontCacheFix.shouldReplaceFontRenderer() && cir.getReturnValue() == FontStorage.GlyphPair.MISSING) {
|
||||
cir.setReturnValue(new FontStorage.GlyphPair(FontCacheFix.BuiltinEmptyGlyph1_12_2.VERY_MISSING, FontCacheFix.BuiltinEmptyGlyph1_12_2.VERY_MISSING));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Inject(method = "findGlyphRenderer", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/Font;getGlyph(I)Lnet/minecraft/client/font/Glyph;"), locals = LocalCapture.CAPTURE_FAILHARD, cancellable = true)
|
||||
public void injectFindGlyphRenderer(int codePoint, CallbackInfoReturnable<GlyphRenderer> cir, Iterator var2, Font font) {
|
||||
if (!obfuscation && ProtocolHack.getTargetVersion().isOlderThanOrEqualTo(VersionEnum.r1_19_4) && this.id.getNamespace().equals("minecraft")) {
|
||||
if (isForbiddenCharacter(font, codePoint)) cir.setReturnValue(this.blankGlyphRenderer);
|
||||
|
||||
if (FontCacheFix.shouldReplaceFontRenderer() && cir.getReturnValue() == this.blankGlyphRenderer) {
|
||||
cir.setReturnValue(FontCacheFix.BuiltinEmptyGlyph1_12_2.VERY_MISSING.bake(this::getGlyphRenderer));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Minecraft uses all characters that exist for obfuscation mode, even those that no longer exist in the selected target version,
|
||||
so we must not make the fix in case it is executed from an obfuscated text, because otherwise the obfuscation would have missing characters
|
||||
*/
|
||||
|
||||
@Inject(method = "getObfuscatedGlyphRenderer", at = @At("HEAD"))
|
||||
public void trackObfuscationState(Glyph glyph, CallbackInfoReturnable<GlyphRenderer> cir) {
|
||||
obfuscation = true;
|
||||
}
|
||||
|
||||
@Inject(method = "getGlyphRenderer(I)Lnet/minecraft/client/font/GlyphRenderer;", at = @At("RETURN"))
|
||||
public void revertObfuscationState(int codePoint, CallbackInfoReturnable<GlyphRenderer> cir) {
|
||||
obfuscation = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viafabricplus_clearCaches() {
|
||||
this.glyphRendererCache.clear();
|
||||
this.glyphCache.clear();
|
||||
}
|
||||
}
|
@ -1,36 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
|
||||
* Copyright (C) 2021-2023 FlorianMichael/EnZaXD and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package de.florianmichael.viafabricplus.injection.mixin.fixes.minecraft.font;
|
||||
|
||||
import de.florianmichael.viafabricplus.definition.FontCacheFix;
|
||||
import net.minecraft.client.font.BuiltinEmptyGlyph;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(BuiltinEmptyGlyph.class)
|
||||
public class MixinBuiltinEmptyGlyph {
|
||||
|
||||
@Inject(method = "getAdvance", at = @At("HEAD"), cancellable = true)
|
||||
public void resetAdvance(CallbackInfoReturnable<Float> cir) {
|
||||
if (FontCacheFix.shouldReplaceFontRenderer()) {
|
||||
cir.setReturnValue(0F);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,35 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
|
||||
* Copyright (C) 2021-2023 FlorianMichael/EnZaXD and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package de.florianmichael.viafabricplus.injection.mixin.fixes.minecraft.font;
|
||||
|
||||
import de.florianmichael.viafabricplus.definition.FontCacheFix;
|
||||
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.CallbackInfoReturnable;
|
||||
|
||||
@Mixin(targets = "net/minecraft/client/font/BuiltinEmptyGlyph$1")
|
||||
public class MixinBuiltinEmptyGlyph_1 {
|
||||
|
||||
@Inject(method = {"getWidth", "getHeight"}, at = @At("HEAD"), cancellable = true)
|
||||
public void resetDimension(CallbackInfoReturnable<Integer> cir) {
|
||||
if (FontCacheFix.shouldReplaceFontRenderer()) {
|
||||
cir.setReturnValue(0);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,80 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
|
||||
* Copyright (C) 2021-2023 FlorianMichael/EnZaXD and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package de.florianmichael.viafabricplus.injection.mixin.fixes.minecraft.font;
|
||||
|
||||
import de.florianmichael.viafabricplus.definition.FontCacheFix;
|
||||
import de.florianmichael.viafabricplus.injection.access.IFontStorage;
|
||||
import net.minecraft.client.font.Font;
|
||||
import net.minecraft.client.font.FontStorage;
|
||||
import net.minecraft.client.font.GlyphRenderer;
|
||||
import net.minecraft.client.font.RenderableGlyph;
|
||||
import org.spongepowered.asm.mixin.Final;
|
||||
import org.spongepowered.asm.mixin.Mixin;
|
||||
import org.spongepowered.asm.mixin.Shadow;
|
||||
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.CallbackInfo;
|
||||
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mixin(FontStorage.class)
|
||||
public abstract class MixinFontStorage implements IFontStorage {
|
||||
|
||||
@Shadow protected abstract GlyphRenderer getGlyphRenderer(RenderableGlyph c);
|
||||
|
||||
@Shadow @Final private List<Font> fonts;
|
||||
|
||||
@Shadow public abstract void setFonts(List<Font> fonts);
|
||||
|
||||
@Unique
|
||||
private GlyphRenderer unknownGlyphRenderer;
|
||||
|
||||
@Unique
|
||||
private List<Font> viafabricplus_fontCache;
|
||||
|
||||
@Inject(method = "setFonts", at = @At("HEAD"))
|
||||
public void cacheFonts(List<Font> fonts, CallbackInfo ci) {
|
||||
if (viafabricplus_fontCache == fonts) return;
|
||||
|
||||
viafabricplus_fontCache = fonts;
|
||||
}
|
||||
|
||||
@Inject(method = "setFonts", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/font/BuiltinEmptyGlyph;bake(Ljava/util/function/Function;)Lnet/minecraft/client/font/GlyphRenderer;", ordinal = 0, shift = At.Shift.AFTER))
|
||||
public void injectSetFonts(List<Font> fonts, CallbackInfo ci) {
|
||||
this.unknownGlyphRenderer = FontCacheFix.BuiltinEmptyGlyph1_12_2.VERY_MISSING.bake(this::getGlyphRenderer);
|
||||
}
|
||||
|
||||
@Inject(method = "getRectangleRenderer", at = @At("HEAD"), cancellable = true)
|
||||
public void setCustomRenderer(CallbackInfoReturnable<GlyphRenderer> cir) {
|
||||
if (FontCacheFix.shouldReplaceFontRenderer()) {
|
||||
cir.setReturnValue(this.unknownGlyphRenderer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void viafabricplus_clearCaches() {
|
||||
if (fonts != null) {
|
||||
fonts.clear();
|
||||
if (viafabricplus_fontCache != null) {
|
||||
setFonts(viafabricplus_fontCache);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,52 @@
|
||||
/*
|
||||
* This file is part of ViaFabricPlus - https://github.com/FlorianMichael/ViaFabricPlus
|
||||
* Copyright (C) 2021-2023 FlorianMichael/EnZaXD and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package de.florianmichael.viafabricplus.mappings;
|
||||
|
||||
import com.google.gson.*;
|
||||
import de.florianmichael.viafabricplus.ViaFabricPlus;
|
||||
import net.minecraft.util.Identifier;
|
||||
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class CharacterMappings {
|
||||
public static Map<String, Map<String, List<Integer>>> forbiddenCharacters1_19_4To1_20 = new HashMap<>();
|
||||
|
||||
public static void load() {
|
||||
final JsonObject file = ViaFabricPlus.GSON.fromJson(new InputStreamReader(CharacterMappings.class.getResourceAsStream("/assets/viafabricplus/characters1_19_4To1_20.txt")), JsonObject.class);
|
||||
for (Map.Entry<String, JsonElement> layer1 : file.entrySet()) { // missing / uniform / bitmap
|
||||
final Map<String, List<Integer>> typeStorage = new HashMap<>();
|
||||
for (Map.Entry<String, JsonElement> layer2 : layer1.getValue().getAsJsonObject().entrySet()) { // blank / space / unicode
|
||||
final List<Integer> type2Storage = new ArrayList<>();
|
||||
for (JsonElement element : layer2.getValue().getAsJsonArray()) { // data
|
||||
type2Storage.add(element.getAsInt());
|
||||
}
|
||||
typeStorage.put(layer2.getKey(), type2Storage);
|
||||
}
|
||||
forbiddenCharacters1_19_4To1_20.put(layer1.getKey(), typeStorage);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, List<Integer>> getForbiddenCharactersForID(final Identifier id) {
|
||||
if (!forbiddenCharacters1_19_4To1_20.containsKey(id.toString())) return new HashMap<>();
|
||||
return forbiddenCharacters1_19_4To1_20.get(id.toString());
|
||||
}
|
||||
}
|
595672
src/main/resources/assets/viafabricplus/characters1_19_4to1_20.txt
Normal file
595672
src/main/resources/assets/viafabricplus/characters1_19_4to1_20.txt
Normal file
File diff suppressed because it is too large
Load Diff
@ -11,7 +11,10 @@ accessible field net/minecraft/client/font/FontManager fontStorages Ljava/util/M
|
||||
accessible field net/minecraft/network/ClientConnection EPOLL_CLIENT_IO_GROUP Lnet/minecraft/util/Lazy;
|
||||
accessible field net/minecraft/network/ClientConnection LOCAL_CLIENT_IO_GROUP Lnet/minecraft/util/Lazy;
|
||||
accessible field net/minecraft/client/gui/screen/GameMenuScreen exitButton Lnet/minecraft/client/gui/widget/ButtonWidget;
|
||||
accessible field net/minecraft/client/font/FontStorage$GlyphPair MISSING Lnet/minecraft/client/font/FontStorage$GlyphPair;
|
||||
|
||||
accessible method net/minecraft/screen/GenericContainerScreenHandler <init> (Lnet/minecraft/screen/ScreenHandlerType;ILnet/minecraft/entity/player/PlayerInventory;I)V
|
||||
accessible method net/minecraft/client/font/FontStorage$GlyphPair <init> (Lnet/minecraft/client/font/Glyph;Lnet/minecraft/client/font/Glyph;)V
|
||||
|
||||
accessible class net/minecraft/client/gui/screen/GameModeSelectionScreen$GameModeSelection
|
||||
accessible class net/minecraft/client/font/FontStorage$GlyphPair
|
||||
|
@ -86,9 +86,7 @@
|
||||
"fixes.minecraft.entity.MixinSquidEntity",
|
||||
"fixes.minecraft.entity.MixinVexEntity",
|
||||
"fixes.minecraft.entity.MixinWolfEntity",
|
||||
"fixes.minecraft.font.MixinBuiltinEmptyGlyph",
|
||||
"fixes.minecraft.font.MixinBuiltinEmptyGlyph_1",
|
||||
"fixes.minecraft.font.MixinFontStorage",
|
||||
"fixes.minecraft.MixinFontStorage",
|
||||
"fixes.minecraft.input.MixinKeyboard",
|
||||
"fixes.minecraft.input.MixinKeyboardInput",
|
||||
"fixes.minecraft.input.MixinMouse",
|
||||
|
Loading…
Reference in New Issue
Block a user