From cba8279c5d1c033fddd3392956dfca93dd7c4141 Mon Sep 17 00:00:00 2001 From: kirillsaint Date: Fri, 14 Jul 2023 03:53:46 +0600 Subject: [PATCH 1/2] test --- .../java/net/silentclient/client/Client.java | 11 + .../client/mixin/SilentClientTransformer.java | 38 +- .../client/mixin/SilentClientTweaker.java | 6 +- .../mixin/accessors/FontRendererAccessor.java | 71 +++ .../accessors/GlStateManagerAccessor.java | 23 + .../accessors/optifine/ConfigAccessor.java | 16 + .../optifine/CustomColorsAccessor.java | 16 + .../client/mixin/ducks/FontRendererExt.java | 9 + .../mixin/mixins/FontRendererMixin.java | 46 +- .../mixin/mixins/GlStateManagerMixin.java | 5 +- .../optifine/OptiFineFontRendererHandler.java | 26 + .../client/utils/FontRendererHook.java | 571 ++++++++++++++++++ src/main/resources/mixins.SilentClient.json | 5 +- 13 files changed, 802 insertions(+), 41 deletions(-) create mode 100644 src/main/java/net/silentclient/client/mixin/accessors/GlStateManagerAccessor.java create mode 100644 src/main/java/net/silentclient/client/mixin/accessors/optifine/ConfigAccessor.java create mode 100644 src/main/java/net/silentclient/client/mixin/accessors/optifine/CustomColorsAccessor.java create mode 100644 src/main/java/net/silentclient/client/mixin/ducks/FontRendererExt.java create mode 100644 src/main/java/net/silentclient/client/mixin/optifine/OptiFineFontRendererHandler.java create mode 100644 src/main/java/net/silentclient/client/utils/FontRendererHook.java diff --git a/src/main/java/net/silentclient/client/Client.java b/src/main/java/net/silentclient/client/Client.java index ae00139..1c9c51c 100644 --- a/src/main/java/net/silentclient/client/Client.java +++ b/src/main/java/net/silentclient/client/Client.java @@ -3,6 +3,7 @@ package net.silentclient.client; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; import net.minecraft.client.gui.GuiScreen; import net.minecraft.client.gui.ScaledResolution; import net.minecraft.client.resources.data.IMetadataSerializer; @@ -24,6 +25,7 @@ import net.silentclient.client.gui.modmenu.ModMenu; import net.silentclient.client.gui.silentmainmenu.SilentMainMenu; import net.silentclient.client.gui.util.BackgroundPanorama; import net.silentclient.client.keybinds.KeyBindManager; +import net.silentclient.client.mixin.ducks.FontRendererExt; import net.silentclient.client.mods.ModInstances; import net.silentclient.client.mods.SettingsManager; import net.silentclient.client.mods.settings.FPSBoostMod; @@ -89,6 +91,8 @@ public class Client { private GlobalSettings globalSettings; private File globalSettingsFile; + private boolean loadedGalacticFontRenderer; + public static void memoryDebug(String paramString) { LogManager.getLogger().info("-- Start Memory Debug -- " + paramString); long l1 = Runtime.getRuntime().maxMemory(); @@ -314,6 +318,13 @@ public class Client { logger.info("STARTING > ERROR: " + err.getMessage()); throw err; } + if (!loadedGalacticFontRenderer) { + loadedGalacticFontRenderer = true; + FontRenderer galacticFontRenderer = Minecraft.getMinecraft().standardGalacticFontRenderer; + if (galacticFontRenderer instanceof FontRendererExt) { + ((FontRendererExt) galacticFontRenderer).silent$getFontRendererHook().create(); + } + } logger.info("-------------------------------------------------"); memoryDebug("CLIENT_POST_INIT"); } diff --git a/src/main/java/net/silentclient/client/mixin/SilentClientTransformer.java b/src/main/java/net/silentclient/client/mixin/SilentClientTransformer.java index 44200fb..c6d0954 100644 --- a/src/main/java/net/silentclient/client/mixin/SilentClientTransformer.java +++ b/src/main/java/net/silentclient/client/mixin/SilentClientTransformer.java @@ -18,43 +18,7 @@ public class SilentClientTransformer implements IClassTransformer { return null; } - if(transformedName.equals("bfl$e")) { - Client.logger.info("Transforming class: " + transformedName); - ClassReader classreader = new ClassReader(bytes); - ClassWriter classwriter = new ClassWriter(1); - SilentClientTransformer.AccessTransformerVisitor visitor = new SilentClientTransformer.AccessTransformerVisitor(262144, classwriter); - classreader.accept(visitor, 0); - return classwriter.toByteArray(); - } - - if(transformedName.equals("bfl$r")) { - Client.logger.info("Transforming class: " + transformedName); - ClassReader classreader = new ClassReader(bytes); - ClassWriter classwriter = new ClassWriter(1); - SilentClientTransformer.AccessTransformerVisitor visitor = new SilentClientTransformer.AccessTransformerVisitor(262144, classwriter); - classreader.accept(visitor, 0); - return classwriter.toByteArray(); - } - - if(transformedName.equals("bnm$a")) { - Client.logger.info("Transforming class: " + transformedName); - ClassReader classreader = new ClassReader(bytes); - ClassWriter classwriter = new ClassWriter(1); - SilentClientTransformer.AccessTransformerVisitor visitor = new SilentClientTransformer.AccessTransformerVisitor(262144, classwriter); - classreader.accept(visitor, 0); - return classwriter.toByteArray(); - } - - if(transformedName.equals("ajd$a")) { - Client.logger.info("Transforming class: " + transformedName); - ClassReader classreader = new ClassReader(bytes); - ClassWriter classwriter = new ClassWriter(1); - SilentClientTransformer.AccessTransformerVisitor visitor = new SilentClientTransformer.AccessTransformerVisitor(262144, classwriter); - classreader.accept(visitor, 0); - return classwriter.toByteArray(); - } - - if(transformedName.equals("bha$a")) { + if(transformedName.equals("bfl$e") || transformedName.equals("bfl$r") || transformedName.equals("bnm$a") || transformedName.equals("ajd$a") || transformedName.equals("bha$a")) { Client.logger.info("Transforming class: " + transformedName); ClassReader classreader = new ClassReader(bytes); ClassWriter classwriter = new ClassWriter(1); diff --git a/src/main/java/net/silentclient/client/mixin/SilentClientTweaker.java b/src/main/java/net/silentclient/client/mixin/SilentClientTweaker.java index 2894b8e..322d20d 100644 --- a/src/main/java/net/silentclient/client/mixin/SilentClientTweaker.java +++ b/src/main/java/net/silentclient/client/mixin/SilentClientTweaker.java @@ -1,14 +1,18 @@ package net.silentclient.client.mixin; import net.minecraft.launchwrapper.ITweaker; +import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; import net.silentclient.client.Client; import org.spongepowered.asm.launch.MixinBootstrap; import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.Mixins; import java.io.File; +import java.lang.reflect.Field; import java.util.ArrayList; import java.util.List; +import java.util.Set; public class SilentClientTweaker implements ITweaker { private List launchArgs = new ArrayList<>(); @@ -55,7 +59,7 @@ public class SilentClientTweaker implements ITweaker { Client.logger.info("Silent Client Tweak Loader: Injecting into class loader"); MixinBootstrap.init(); MixinEnvironment environment = MixinEnvironment.getDefaultEnvironment(); - environment.addConfiguration("mixins.SilentClient.json"); + Mixins.addConfiguration("mixins.SilentClient.json"); if (environment.getObfuscationContext() == null) { environment.setObfuscationContext("notch"); diff --git a/src/main/java/net/silentclient/client/mixin/accessors/FontRendererAccessor.java b/src/main/java/net/silentclient/client/mixin/accessors/FontRendererAccessor.java index 006605f..70b8b0d 100644 --- a/src/main/java/net/silentclient/client/mixin/accessors/FontRendererAccessor.java +++ b/src/main/java/net/silentclient/client/mixin/accessors/FontRendererAccessor.java @@ -1,10 +1,81 @@ package net.silentclient.client.mixin.accessors; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.texture.TextureManager; +import net.minecraft.util.ResourceLocation; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; @Mixin(FontRenderer.class) public interface FontRendererAccessor { @Invoker("renderString") int silent$renderString(String text, float x, float y, int color, boolean dropShadow); + + @Accessor + float getPosX(); + + @Accessor + float getPosY(); + + @Accessor + void setPosX(float posX); + + @Accessor + void setPosY(float posY); + + @Accessor + float getRed(); + + // Someone messed up the mappings + @Accessor("green") + float getBlue(); + + // Someone messed up the mappings + @Accessor("blue") + float getGreen(); + + @Accessor + float getAlpha(); + + @Accessor + void setStrikethroughStyle(boolean b); + + @Accessor + void setUnderlineStyle(boolean b); + + @Accessor + void setItalicStyle(boolean b); + + @Accessor + void setRandomStyle(boolean b); + + @Accessor + void setBoldStyle(boolean b); + + @Accessor + boolean isRandomStyle(); + + @Accessor + boolean isItalicStyle(); + + @Accessor + boolean isBoldStyle(); + + @Accessor + boolean isStrikethroughStyle(); + + @Accessor + boolean isUnderlineStyle(); + + @Accessor + byte[] getGlyphWidth(); + + @Accessor + void setTextColor(int color); + + @Accessor + int[] getColorCode(); + + @Invoker + int invokeRenderString(String text, float x, float y, int color, boolean dropShadow); } diff --git a/src/main/java/net/silentclient/client/mixin/accessors/GlStateManagerAccessor.java b/src/main/java/net/silentclient/client/mixin/accessors/GlStateManagerAccessor.java new file mode 100644 index 0000000..2f39970 --- /dev/null +++ b/src/main/java/net/silentclient/client/mixin/accessors/GlStateManagerAccessor.java @@ -0,0 +1,23 @@ +package net.silentclient.client.mixin.accessors; + +import net.minecraft.client.renderer.GlStateManager; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.gen.Accessor; + +@Mixin(GlStateManager.class) +public interface GlStateManagerAccessor { + @Accessor + static GlStateManager.Color getColorState() { + throw new UnsupportedOperationException("Mixin failed to inject!"); + } + + @Accessor + static GlStateManager.TextureState[] getTextureState() { + throw new UnsupportedOperationException("Mixin failed to inject!"); + } + + @Accessor + static int getActiveTextureUnit() { + throw new UnsupportedOperationException("Mixin failed to inject!"); + } +} \ No newline at end of file diff --git a/src/main/java/net/silentclient/client/mixin/accessors/optifine/ConfigAccessor.java b/src/main/java/net/silentclient/client/mixin/accessors/optifine/ConfigAccessor.java new file mode 100644 index 0000000..471b506 --- /dev/null +++ b/src/main/java/net/silentclient/client/mixin/accessors/optifine/ConfigAccessor.java @@ -0,0 +1,16 @@ +package net.silentclient.client.mixin.accessors.optifine; + +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Pseudo +@Mixin(targets = "Config") +public interface ConfigAccessor { + @Dynamic("OptiFine") + @Invoker + static boolean invokeIsCustomColors() { + throw new AssertionError("Mixin did not inject"); + } +} \ No newline at end of file diff --git a/src/main/java/net/silentclient/client/mixin/accessors/optifine/CustomColorsAccessor.java b/src/main/java/net/silentclient/client/mixin/accessors/optifine/CustomColorsAccessor.java new file mode 100644 index 0000000..ed37d3d --- /dev/null +++ b/src/main/java/net/silentclient/client/mixin/accessors/optifine/CustomColorsAccessor.java @@ -0,0 +1,16 @@ +package net.silentclient.client.mixin.accessors.optifine; + +import org.spongepowered.asm.mixin.Dynamic; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Pseudo; +import org.spongepowered.asm.mixin.gen.Invoker; + +@Pseudo +@Mixin(targets = "net.optifine.CustomColors") +public interface CustomColorsAccessor { + @Dynamic("OptiFine") + @Invoker + public static int invokeGetTextColor(int index, int color) { + throw new AssertionError("Mixin failed to inject into OptiFine"); + } +} \ No newline at end of file diff --git a/src/main/java/net/silentclient/client/mixin/ducks/FontRendererExt.java b/src/main/java/net/silentclient/client/mixin/ducks/FontRendererExt.java new file mode 100644 index 0000000..7536ab4 --- /dev/null +++ b/src/main/java/net/silentclient/client/mixin/ducks/FontRendererExt.java @@ -0,0 +1,9 @@ +package net.silentclient.client.mixin.ducks; + +import net.silentclient.client.cosmetics.StaticResourceLocation; +import net.silentclient.client.utils.FontRendererHook; + +public interface FontRendererExt { + FontRendererHook silent$getFontRendererHook(); + StaticResourceLocation silent$getFontTextures(); +} diff --git a/src/main/java/net/silentclient/client/mixin/mixins/FontRendererMixin.java b/src/main/java/net/silentclient/client/mixin/mixins/FontRendererMixin.java index 3f3d471..f39e820 100644 --- a/src/main/java/net/silentclient/client/mixin/mixins/FontRendererMixin.java +++ b/src/main/java/net/silentclient/client/mixin/mixins/FontRendererMixin.java @@ -1,16 +1,58 @@ package net.silentclient.client.mixin.mixins; import net.minecraft.client.gui.FontRenderer; +import net.minecraft.util.ResourceLocation; +import net.silentclient.client.cosmetics.StaticResourceLocation; import net.silentclient.client.event.impl.EventText; +import net.silentclient.client.mixin.ducks.FontRendererExt; +import net.silentclient.client.mods.settings.FPSBoostMod; +import net.silentclient.client.utils.FontRendererHook; +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.ModifyVariable; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; +import java.util.Map; + @Mixin(value = FontRenderer.class, priority = 1100) -public abstract class FontRendererMixin { +public abstract class FontRendererMixin implements FontRendererExt { + @Unique + private final FontRendererHook silent$fontRendererHook = new FontRendererHook((FontRenderer) (Object) this); + + @Inject(method = "getStringWidth", at = @At("HEAD"), cancellable = true) + public void getStringWidth(String text, CallbackInfoReturnable cir) { + if (FPSBoostMod.basicEnabled()) { + cir.setReturnValue(this.silent$fontRendererHook.getStringWidth(text)); + } else { + Map cache = this.silent$fontRendererHook.getEnhancedFontRenderer().getStringWidthCache(); + if (cache.size() != 0) { + cache.clear(); + } + } + } + + @Override + public StaticResourceLocation silent$getFontTextures() { + return new StaticResourceLocation(this.locationFontTexture); + } + + @Inject(method = "renderStringAtPos", at = @At("HEAD"), cancellable = true) + private void silent$useOptimizedRendering(String text, boolean shadow, CallbackInfo ci) { + if (this.silent$fontRendererHook.renderStringAtPos(text, shadow)) { + ci.cancel(); + } + } + + @Override + public FontRendererHook silent$getFontRendererHook() { + return silent$fontRendererHook; + } + @ModifyVariable(method = "renderString", at = @At("HEAD"), ordinal = 0) public String renderString(String text) { if(text == null) { @@ -38,6 +80,8 @@ public abstract class FontRendererMixin { @Shadow protected abstract void resetStyles(); + @Shadow @Final private ResourceLocation locationFontTexture; + @Inject(method = "drawString(Ljava/lang/String;FFIZ)I", at = @At( value = "INVOKE", target = "Lnet/minecraft/client/gui/FontRenderer;renderString(Ljava/lang/String;FFIZ)I", diff --git a/src/main/java/net/silentclient/client/mixin/mixins/GlStateManagerMixin.java b/src/main/java/net/silentclient/client/mixin/mixins/GlStateManagerMixin.java index b8d19e2..c162f71 100644 --- a/src/main/java/net/silentclient/client/mixin/mixins/GlStateManagerMixin.java +++ b/src/main/java/net/silentclient/client/mixin/mixins/GlStateManagerMixin.java @@ -5,12 +5,15 @@ import net.minecraft.client.renderer.OpenGlHelper; import net.silentclient.client.utils.HUDCaching; import org.lwjgl.opengl.GL11; import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; @Mixin(GlStateManager.class) -public class GlStateManagerMixin { +public abstract class GlStateManagerMixin { + @Shadow private static GlStateManager.Color colorState; + @Shadow private static GlStateManager.TextureState[] textureState; private static boolean blendEnabled; @Inject(method = "blendFunc(II)V", at = @At("HEAD"), cancellable = true) diff --git a/src/main/java/net/silentclient/client/mixin/optifine/OptiFineFontRendererHandler.java b/src/main/java/net/silentclient/client/mixin/optifine/OptiFineFontRendererHandler.java new file mode 100644 index 0000000..76150d7 --- /dev/null +++ b/src/main/java/net/silentclient/client/mixin/optifine/OptiFineFontRendererHandler.java @@ -0,0 +1,26 @@ +package net.silentclient.client.mixin.optifine; + +import net.silentclient.client.Client; +import net.silentclient.client.mixin.SilentClientTweaker; +import net.silentclient.client.mixin.accessors.optifine.ConfigAccessor; +import net.silentclient.client.mixin.accessors.optifine.CustomColorsAccessor; + +public class OptiFineFontRendererHandler { + private static boolean caughtError = false; + public static int getTextColor(int index, int originalColor) { + if (caughtError) return originalColor; + try { + if (!SilentClientTweaker.hasOptifine) { + return originalColor; + } + if (ConfigAccessor.invokeIsCustomColors()) { + return CustomColorsAccessor.invokeGetTextColor(index, originalColor); + } + return originalColor; + } catch (Throwable t) { + caughtError = true; + Client.logger.error("Unable to get OptiFine's Custom Color", t); + return originalColor; + } + } +} \ No newline at end of file diff --git a/src/main/java/net/silentclient/client/utils/FontRendererHook.java b/src/main/java/net/silentclient/client/utils/FontRendererHook.java new file mode 100644 index 0000000..77f27c1 --- /dev/null +++ b/src/main/java/net/silentclient/client/utils/FontRendererHook.java @@ -0,0 +1,571 @@ +package net.silentclient.client.utils; + +import kotlin.Pair; +import net.minecraft.client.Minecraft; +import net.minecraft.client.gui.FontRenderer; +import net.minecraft.client.renderer.GlStateManager; +import net.minecraft.client.renderer.texture.DynamicTexture; +import net.minecraft.util.ResourceLocation; +import net.silentclient.client.Client; +import net.silentclient.client.hooks.OptiFineHook; +import net.silentclient.client.mixin.accessors.FontRendererAccessor; +import net.silentclient.client.mixin.accessors.GlStateManagerAccessor; +import net.silentclient.client.mixin.ducks.FontRendererExt; +import net.silentclient.client.mixin.optifine.OptiFineFontRendererHandler; +import net.silentclient.client.mods.settings.FPSBoostMod; +import net.silentclient.client.utils.enhancement.EnhancementManager; +import net.silentclient.client.utils.enhancement.hash.StringHash; +import net.silentclient.client.utils.enhancement.text.CachedString; +import net.silentclient.client.utils.enhancement.text.EnhancedFontRenderer; +import org.lwjgl.opengl.GL11; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.IOException; +import java.io.InputStream; +import java.util.*; + +public final class FontRendererHook { + + public static boolean forceRefresh = false; + public static final String characterDictionary = "\u00c0\u00c1\u00c2\u00c8\u00ca\u00cb\u00cd\u00d3\u00d4\u00d5\u00da\u00df\u00e3\u00f5\u011f\u0130\u0131\u0152\u0153\u015e\u015f\u0174\u0175\u017e\u0207\u0000\u0000\u0000\u0000\u0000\u0000\u0000 !\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~\u0000\u00c7\u00fc\u00e9\u00e2\u00e4\u00e0\u00e5\u00e7\u00ea\u00eb\u00e8\u00ef\u00ee\u00ec\u00c4\u00c5\u00c9\u00e6\u00c6\u00f4\u00f6\u00f2\u00fb\u00f9\u00ff\u00d6\u00dc\u00f8\u00a3\u00d8\u00d7\u0192\u00e1\u00ed\u00f3\u00fa\u00f1\u00d1\u00aa\u00ba\u00bf\u00ae\u00ac\u00bd\u00bc\u00a1\u00ab\u00bb\u2591\u2592\u2593\u2502\u2524\u2561\u2562\u2556\u2555\u2563\u2551\u2557\u255d\u255c\u255b\u2510\u2514\u2534\u252c\u251c\u2500\u253c\u255e\u255f\u255a\u2554\u2569\u2566\u2560\u2550\u256c\u2567\u2568\u2564\u2565\u2559\u2558\u2552\u2553\u256b\u256a\u2518\u250c\u2588\u2584\u258c\u2590\u2580\u03b1\u03b2\u0393\u03c0\u03a3\u03c3\u03bc\u03c4\u03a6\u0398\u03a9\u03b4\u221e\u2205\u2208\u2229\u2261\u00b1\u2265\u2264\u2320\u2321\u00f7\u2248\u00b0\u2219\u00b7\u221a\u207f\u00b2\u25a0\u0000"; + private final EnhancedFontRenderer enhancedFontRenderer = EnhancementManager.getInstance().getEnhancement(EnhancedFontRenderer.class); + private final FontRenderer fontRenderer; + private final FontRendererAccessor fontRendererAccessor; + private final Minecraft mc = Minecraft.getMinecraft(); + private OptiFineHook hook = new OptiFineHook(); + public int glTextureId = -1; + private int texSheetDim = 256; + private float fontTexHeight = 16 * texSheetDim + 128; + private float fontTexWidth = 16 * texSheetDim; + private int regularCharDim = 128; + private boolean drawing = false; + private static final String COLOR_RESET_PHRASE = '\u00a7' + "r"; + + public FontRendererHook(FontRenderer fontRenderer) { + this.fontRenderer = fontRenderer; + this.fontRendererAccessor = (FontRendererAccessor) fontRenderer; + } + + private void establishSize() { + int regWidth = 256; + for (int i = 0; i < 256; i++) { + try (final InputStream stream = mc.getResourceManager().getResource(new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", i))).getInputStream()) { + regWidth = ImageIO.read(stream).getWidth(); + break; + } catch (Exception ignored) { + } + } + + texSheetDim = regWidth; + int specWidth = 128; + + try (final InputStream stream = mc.getResourceManager().getResource(((FontRendererExt) fontRenderer).silent$getFontTextures().getLocation()).getInputStream()) { + specWidth = ImageIO.read(stream).getWidth(); + } catch (IOException e) { + Client.logger.error("Failed to read font texture while establishing size.", e); + } + + regularCharDim = specWidth; + fontTexHeight = 16 * texSheetDim + specWidth; + fontTexWidth = 16 * texSheetDim; + } + + public void create() { + establishSize(); + hook = new OptiFineHook(); + forceRefresh = false; + + if (glTextureId != -1) { + GlStateManager.deleteTexture(glTextureId); + } + + final BufferedImage bufferedImage = new BufferedImage((int) fontTexWidth, (int) fontTexHeight, BufferedImage.TYPE_INT_ARGB); + for (int i = 0; i < 256; i++) { + try (final InputStream stream = mc.getResourceManager().getResource(new ResourceLocation(String.format("textures/font/unicode_page_%02x.png", i))).getInputStream()) { + bufferedImage.getGraphics().drawImage(ImageIO.read(stream), i / 16 * texSheetDim, i % 16 * texSheetDim, null); + if (i == 0x0d) { + try (final InputStream sustream = FontRendererHook.class.getResourceAsStream("/assets/patcher/font_glyph_data.bin")) { + Graphics2D graphics = bufferedImage.createGraphics(); + graphics.setComposite(AlphaComposite.Src); + graphics.drawImage(ImageIO.read(Objects.requireNonNull(sustream)), 0x9DE % 16 * 16 /* 224 */, (i % 16 * texSheetDim) + (0x9DE / 16 - 16 /* 141 */), null); + fontRendererAccessor.getGlyphWidth()[0xD9E] = 14; + } + } + } catch (Exception ignored) { + } + } + + try (final InputStream stream = mc.getResourceManager().getResource(((FontRendererExt) fontRenderer).silent$getFontTextures().getLocation()).getInputStream()) { + bufferedImage.getGraphics().drawImage(ImageIO.read(stream), 0, 16 * texSheetDim, null); + } catch (IOException e) { + Client.logger.error("Failed to draw texture sheet.", e); + } + + glTextureId = new DynamicTexture(bufferedImage).getGlTextureId(); + } + + private void deleteTextureId() { + if (glTextureId != -1) { + GlStateManager.deleteTexture(glTextureId); + glTextureId = -1; + } + } + + public static String clearColorReset(String text) { + int startIndex = 0; + int endIndex = text.length(); + + while (text.indexOf(COLOR_RESET_PHRASE, startIndex) == startIndex) { + startIndex += 2; + } + int e; + while ((e = text.lastIndexOf(COLOR_RESET_PHRASE, endIndex - 1)) == endIndex - 2 && e != -1) { + endIndex -= 2; + } + + if (endIndex < startIndex) endIndex = startIndex; + + return text.substring(startIndex, endIndex); + } + + @SuppressWarnings({"unused"}) + public boolean renderStringAtPos(String text, boolean shadow) { + if (Minecraft.getMinecraft().getTextureManager() == null || !FPSBoostMod.basicEnabled()) { + this.deleteTextureId(); + return false; + } + + if (glTextureId == -1 || forceRefresh) { + create(); + } + + text = clearColorReset(text); + + if (text.isEmpty()) { + return false; + } + + float posX = this.fontRendererAccessor.getPosX(); + float posY = this.fontRendererAccessor.getPosY(); + this.fontRendererAccessor.setPosY(0.0f); + this.fontRendererAccessor.setPosX(0.0f); + + float red = this.fontRendererAccessor.getRed(); + float green = this.fontRendererAccessor.getGreen(); + float blue = this.fontRendererAccessor.getBlue(); + float alpha = this.fontRendererAccessor.getAlpha(); + + GlStateManager.bindTexture(glTextureId); + GlStateManager.translate(posX, posY, 0F); + + final GlStateManager.TextureState[] textureStates = GlStateManagerAccessor.getTextureState(); + final GlStateManager.TextureState textureState = textureStates[GlStateManagerAccessor.getActiveTextureUnit()]; + + final StringHash hash = new StringHash(text, red, green, blue, alpha, shadow); + final CachedString cachedString = FPSBoostMod.advancedEnabled() ? this.enhancedFontRenderer.get(hash) : null; + + if (cachedString != null) { + GlStateManager.color(red, green, blue, alpha); + GlStateManager.callList(cachedString.getListId()); + + // Call so states in game know the texture was changed. + // Otherwise, the game won't know the active texture was changed on the GPU + textureState.textureName = glTextureId; + + // Save thing as texture, it updated in GL, so we need to update the MC cache of that value + GlStateManager.Color colorState = GlStateManagerAccessor.getColorState(); + colorState.red = cachedString.getLastRed(); + colorState.green = cachedString.getLastGreen(); + colorState.blue = cachedString.getLastBlue(); + colorState.alpha = cachedString.getLastAlpha(); + GlStateManager.translate(-posX, -posY, 0.0f); + GlStateManager.resetColor(); + GlStateManager.color(1, 1, 1, 1); + + this.fontRendererAccessor.setPosX(posX + cachedString.getWidth()); + this.fontRendererAccessor.setPosY(posY + cachedString.getHeight()); + return true; + } + + int list = 0; + textureState.textureName = glTextureId; + GlStateManager.resetColor(); + if (FPSBoostMod.advancedEnabled()) { + list = enhancedFontRenderer.getGlList(); + GL11.glNewList(list, GL11.GL_COMPILE_AND_EXECUTE); + } + + boolean obfuscated = false; + final CachedString value = new CachedString(text, list, this.fontRendererAccessor.getPosX() - posX, this.fontRendererAccessor.getPosY() - posY); + final Deque underline = new LinkedList<>(); + final Deque strikethrough = new LinkedList<>(); + + value.setLastRed(red); + value.setLastGreen(green); + value.setLastBlue(blue); + value.setLastAlpha(alpha); + + for (int messageChar = 0; messageChar < text.length(); ++messageChar) { + char letter = text.charAt(messageChar); + + if (letter == 167 && messageChar + 1 < text.length()) { + int styleIndex = "0123456789abcdefklmnor".indexOf(text.toLowerCase(Locale.ENGLISH).charAt(messageChar + 1)); + + if (styleIndex < 16) { + this.fontRendererAccessor.setStrikethroughStyle(false); + this.fontRendererAccessor.setUnderlineStyle(false); + this.fontRendererAccessor.setItalicStyle(false); + this.fontRendererAccessor.setRandomStyle(false); + this.fontRendererAccessor.setBoldStyle(false); + + if (styleIndex < 0) { + styleIndex = 15; + } + + if (shadow) { + styleIndex += 16; + } + + int currentColor = fontRendererAccessor.getColorCode()[styleIndex]; + currentColor = OptiFineFontRendererHandler.getTextColor(styleIndex, currentColor); + this.fontRendererAccessor.setTextColor(currentColor); + + final float colorRed = (float) (currentColor >> 16) / 255.0F; + final float colorGreen = (float) (currentColor >> 8 & 255) / 255.0F; + final float colorBlue = (float) (currentColor & 255) / 255.0F; + + GlStateManager.color(colorRed, colorGreen, colorBlue, alpha); + + value.setLastAlpha(alpha); + value.setLastGreen(colorGreen); + value.setLastBlue(colorBlue); + value.setLastRed(colorRed); + } else if (styleIndex == 16) { + this.fontRendererAccessor.setRandomStyle(true); + obfuscated = true; + } else if (styleIndex == 17) { + this.fontRendererAccessor.setBoldStyle(true); + } else if (styleIndex == 18) { + this.fontRendererAccessor.setStrikethroughStyle(true); + } else if (styleIndex == 19) { + this.fontRendererAccessor.setUnderlineStyle(true); + } else if (styleIndex == 20) { + this.fontRendererAccessor.setItalicStyle(true); + } else { + this.fontRendererAccessor.setRandomStyle(false); + this.fontRendererAccessor.setBoldStyle(false); + this.fontRendererAccessor.setStrikethroughStyle(false); + this.fontRendererAccessor.setUnderlineStyle(false); + this.fontRendererAccessor.setItalicStyle(false); + GlStateManager.color(red, green, blue, alpha); + + value.setLastGreen(green); + value.setLastAlpha(alpha); + value.setLastBlue(blue); + value.setLastRed(red); + } + + ++messageChar; + } else { + int index = characterDictionary.indexOf(letter); + + if (this.fontRendererAccessor.isRandomStyle() && index != -1) { + final float charWidthFloat = getCharWidthFloat(letter); + char charIndex; + + do { + index = this.fontRenderer.fontRandom.nextInt(characterDictionary.length()); + charIndex = characterDictionary.charAt(index); + } while (charWidthFloat != getCharWidthFloat(charIndex)); + + letter = charIndex; + } + + final boolean unicode = this.fontRenderer.getUnicodeFlag(); + final float boldWidth = getBoldOffset(index); + final boolean small = (letter == 0 || index == -1 || unicode) && shadow; + + if (small) { + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() - boldWidth); + this.fontRendererAccessor.setPosY(fontRendererAccessor.getPosY() - boldWidth); + } + + float effectiveWidth = this.renderChar(letter, this.fontRendererAccessor.isItalicStyle()); + if (small) { + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() + boldWidth); + this.fontRendererAccessor.setPosY(fontRendererAccessor.getPosY() + boldWidth); + } + + if (this.fontRendererAccessor.isBoldStyle()) { + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() + boldWidth); + + if (small) { + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() - boldWidth); + this.fontRendererAccessor.setPosY(fontRendererAccessor.getPosY() - boldWidth); + } + + this.renderChar(letter, this.fontRendererAccessor.isItalicStyle()); + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() -boldWidth); + + if (small) { + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() + boldWidth); + this.fontRendererAccessor.setPosY(fontRendererAccessor.getPosY() + boldWidth); + } + + ++effectiveWidth; + } + + if (this.fontRendererAccessor.isStrikethroughStyle()) { + adjustOrAppend(strikethrough, this.fontRendererAccessor.getPosX(), effectiveWidth, value.getLastRed(), value.getLastGreen(), value.getLastBlue(), value.getLastAlpha()); + } + + if (this.fontRendererAccessor.isUnderlineStyle()) { + adjustOrAppend(underline, this.fontRendererAccessor.getPosX(), effectiveWidth, value.getLastRed(), value.getLastGreen(), value.getLastBlue(), value.getLastAlpha()); + } + + // Intentional cast to int to round down, see FontRenderer#doDraw + this.fontRendererAccessor.setPosX(fontRendererAccessor.getPosX() + (int) effectiveWidth); + } + } + + endDrawing(); + final boolean hasStyle = underline.size() > 0 || strikethrough.size() > 0; + + if (hasStyle) { + GlStateManager.disableTexture2D(); + GL11.glBegin(GL11.GL_QUADS); + + for (final RenderPair renderPair : strikethrough) { + GlStateManager.color(renderPair.red, renderPair.green, renderPair.blue, renderPair.alpha); + GL11.glVertex2f(renderPair.posX, this.fontRendererAccessor.getPosY() + 4.0f); + GL11.glVertex2f(renderPair.posX + renderPair.width, this.fontRendererAccessor.getPosY() + 4.0f); + GL11.glVertex2f(renderPair.posX + renderPair.width, this.fontRendererAccessor.getPosY() + 3.0f); + GL11.glVertex2f(renderPair.posX, this.fontRendererAccessor.getPosY() + 3.0f); + } + + for (final RenderPair renderPair : underline) { + GlStateManager.color(renderPair.red, renderPair.green, renderPair.blue, renderPair.alpha); + GL11.glVertex2f(renderPair.posX - 1.0f, this.fontRendererAccessor.getPosY() + 9); + GL11.glVertex2f(renderPair.posX + renderPair.width, this.fontRendererAccessor.getPosY() + 9); + GL11.glVertex2f(renderPair.posX + renderPair.width, this.fontRendererAccessor.getPosY() + 9 - 1.0F); + GL11.glVertex2f(renderPair.posX - 1.0f, this.fontRendererAccessor.getPosY() + 9 - 1.0F); + } + + GL11.glEnd(); + GlStateManager.enableTexture2D(); + } + + if (FPSBoostMod.advancedEnabled()) { + GL11.glEndList(); + this.enhancedFontRenderer.cache(hash, value); + } + + value.setWidth(this.fontRendererAccessor.getPosX()); + + this.fontRendererAccessor.setPosY(posY + value.getHeight()); + this.fontRendererAccessor.setPosX(posX + value.getWidth()); + + if (obfuscated) { + this.enhancedFontRenderer.getObfuscated().add(hash); + } + + GlStateManager.translate(-posX, -posY, 0F); + return true; + } + + private void adjustOrAppend(Deque style, float posX, float effectiveWidth, float lastRed, float lastGreen, float lastBlue, float lastAlpha) { + final RenderPair lastStart = style.peekLast(); + if (lastStart != null && lastStart.red == lastRed && lastStart.green == lastGreen && lastStart.blue == lastBlue && lastStart.alpha == lastAlpha) { + if (lastStart.posX + lastStart.width >= posX - 1) { + lastStart.width = posX + effectiveWidth - lastStart.posX; + return; + } + } + style.add(new RenderPair(posX, effectiveWidth, lastRed, lastGreen, lastBlue, lastAlpha)); + } + + private float getBoldOffset(int index) { + return index == -1 || fontRenderer.getUnicodeFlag() ? 0.5F : getOptifineBoldOffset(); + } + + private float getOptifineBoldOffset() { + return hook.getOptifineBoldOffset(fontRenderer); + } + + public float renderChar(char ch, boolean italic) { + if (ch == 32 || ch == 160) { + return fontRenderer.getUnicodeFlag() ? 4.0F : getCharWidthFloat(ch); + } else { + final int charIndex = characterDictionary.indexOf(ch); + return charIndex != -1 && !this.fontRenderer.getUnicodeFlag() ? this.renderDefaultChar(charIndex, italic, ch) : this.renderUnicodeChar(ch, italic); + } + } + + /** + * Render a single character with the default.png font at current (posX,posY) location... + */ + private float renderDefaultChar(int characterIndex, boolean italic, char ch) { + final float characterX = (characterIndex % 16 * 8 * regularCharDim >> 7) + .01f; + final float characterY = ((characterIndex >> 4) * 8 * regularCharDim >> 7) + 16 * texSheetDim + .01f; + + final int italicStyle = italic ? 1 : 0; + final float charWidth = getCharWidthFloat(ch); + final float smallCharWidth = charWidth - 0.01F; + + startDrawing(); + final float uvHeight = 7.99F * regularCharDim / 128; + final float uvWidth = smallCharWidth * regularCharDim / 128; + + GL11.glTexCoord2f(characterX / fontTexWidth, characterY / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + (float) italicStyle, this.fontRendererAccessor.getPosY()); + + GL11.glTexCoord2f(characterX / fontTexWidth, (characterY + uvHeight) / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() - (float) italicStyle, this.fontRendererAccessor.getPosY() + 7.99F); + + final int offset = regularCharDim / 128; + GL11.glTexCoord2f((characterX + uvWidth - offset) / fontTexWidth, (characterY + uvHeight) / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + smallCharWidth - 1.0F - (float) italicStyle, this.fontRendererAccessor.getPosY() + 7.99F); + + GL11.glTexCoord2f((characterX + uvWidth - offset) / fontTexWidth, characterY / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + smallCharWidth - 1.0F + (float) italicStyle, this.fontRendererAccessor.getPosY()); + + return charWidth; + } + + private void startDrawing() { + if (!drawing) { + drawing = true; + GL11.glBegin(GL11.GL_QUADS); + } + } + + private void endDrawing() { + if (drawing) { + drawing = false; + GL11.glEnd(); + } + } + + private Pair getUV(char characterIndex) { + final int page = characterIndex / 256; + final int row = page >> 4; + final int column = page % 16; + final int glyphWidth = this.fontRendererAccessor.getGlyphWidth()[characterIndex] >>> 4; + final float charX = (float) (characterIndex % 16 << 4) + glyphWidth + (.05f * page / 39f); + final float charY = (float) (((characterIndex & 255) >> 4) * 16) + (.05f * page / 39f); + return new Pair<>((row * texSheetDim + charX) / fontTexWidth, (column * texSheetDim + charY) / fontTexHeight); //16 rows each with a size of 64px + } + + /** + * Render a single Unicode character at current (posX,posY) location using one of the /font/glyph_XX.png files... + */ + private float renderUnicodeChar(char ch, boolean italic) { + if (this.fontRendererAccessor.getGlyphWidth()[ch] == 0) { + return 0.0F; + } else { + final Pair uv = getUV(ch); + final int glyphX = this.fontRendererAccessor.getGlyphWidth()[ch] >>> 4; + final int glyphY = this.fontRendererAccessor.getGlyphWidth()[ch] & 15; + final float floatGlyphX = (float) glyphX; + final float modifiedY = (float) glyphY + 1; + final float combinedGlyphSize = modifiedY - floatGlyphX - 0.02F; + final float italicStyle = italic ? 1.0F : 0.0F; + startDrawing(); + + final float v = 15.98F * texSheetDim / 256; + GL11.glTexCoord2f(uv.component1(), uv.component2()); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + italicStyle, this.fontRendererAccessor.getPosY()); + + GL11.glTexCoord2f(uv.component1(), uv.component2() + v / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() - italicStyle, this.fontRendererAccessor.getPosY() + 7.99F); + + final float texAdj = combinedGlyphSize + .5f; + GL11.glTexCoord2f(uv.component1() + texAdj / fontTexHeight, uv.component2() + v / fontTexHeight); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + combinedGlyphSize / 2.0F - italicStyle, this.fontRendererAccessor.getPosY() + 7.99F); + + GL11.glTexCoord2f(uv.component1() + texAdj / fontTexHeight, uv.component2()); + GL11.glVertex2f(this.fontRendererAccessor.getPosX() + combinedGlyphSize / 2.0F + italicStyle, this.fontRendererAccessor.getPosY()); + + return (modifiedY - floatGlyphX) / 2.0F + 1.0F; + } + } + + private float getCharWidthFloat(char c) { //Remapped to optifine's stuff if needed + return hook.getCharWidth(fontRenderer, c); + } + + @SuppressWarnings("unused") + public int getStringWidth(String text) { + if (text == null) { + return 0; + } + + Map stringWidthCache = enhancedFontRenderer.getStringWidthCache(); + if (stringWidthCache.size() > 5000) { + stringWidthCache.clear(); + } + + return stringWidthCache.computeIfAbsent(text, width -> getUncachedWidth(text)); + } + + private int getUncachedWidth(String text) { + if (text == null) { + return 0; + } else { + float width = 0; + boolean bold = false; + + for (int messageChar = 0; messageChar < text.length(); ++messageChar) { + char character = text.charAt(messageChar); + float characterWidth = getCharWidthFloat(character); + + if (characterWidth < 0 && messageChar < text.length() - 1) { + ++messageChar; + character = text.charAt(messageChar); + + if (character != 108 && character != 76) { + if (character == 114 || character == 82) { + bold = false; + } + } else { + bold = true; + } + + characterWidth = 0; + } + + width += characterWidth; + + if (bold && characterWidth > 0) { + width += getOptifineBoldOffset(); + } + } + + return (int) width; + } + } + + public EnhancedFontRenderer getEnhancedFontRenderer() { + return enhancedFontRenderer; + } + + static class RenderPair { + private final float red; + private final float green; + private final float blue; + private final float alpha; + float posX; + float width; + + public RenderPair(float posX, float width, float red, float green, float blue, float alpha) { + this.posX = posX; + this.width = width; + this.red = red; + this.green = green; + this.blue = blue; + this.alpha = alpha; + } + } +} \ No newline at end of file diff --git a/src/main/resources/mixins.SilentClient.json b/src/main/resources/mixins.SilentClient.json index 45d7c83..13e86f5 100644 --- a/src/main/resources/mixins.SilentClient.json +++ b/src/main/resources/mixins.SilentClient.json @@ -110,6 +110,9 @@ "mixins.EntityPacketsMixin", "mixins.ScreenShotHelperMixin", "mixins.RenderTNTPrimedMixin", - "mixins.GuiDisconnectedMixin" + "mixins.GuiDisconnectedMixin", + "accessors.GlStateManagerAccessor", + "accessors.optifine.CustomColorsAccessor", + "accessors.optifine.ConfigAccessor" ] } \ No newline at end of file From 4dddc10f647a85367f6f6dff1e0edf34ac70c6ca Mon Sep 17 00:00:00 2001 From: kirillsaint Date: Fri, 21 Jul 2023 23:53:07 +0600 Subject: [PATCH 2/2] font optimization --- .idea/gradle.xml | 3 +++ .idea/misc.xml | 5 +++- .../client/gui/GuiMultiplayerInGame.java | 22 ++++++++++++++++-- .../client/mixin/SilentClientTransformer.java | 5 +--- .../accessors/GlStateManagerAccessor.java | 23 ------------------- .../mixin/mixins/FontRendererMixin.java | 2 +- .../client/utils/FontRendererHook.java | 17 ++++++++++---- src/main/resources/mixins.SilentClient.json | 1 - 8 files changed, 41 insertions(+), 37 deletions(-) delete mode 100644 src/main/java/net/silentclient/client/mixin/accessors/GlStateManagerAccessor.java diff --git a/.idea/gradle.xml b/.idea/gradle.xml index 611e7c8..dd09283 100644 --- a/.idea/gradle.xml +++ b/.idea/gradle.xml @@ -4,8 +4,11 @@