Astra Client 1.3 (Skidded Client)

1) Interesting

Screenshot taken from the announments of channel of Resent's Discord

2) Source Code (Obfuscated Minecraft Class Names (I used a remapper to remap the unobfuscated MC class names to the obfuscated ones))

https://www.mediafire.com/file/k0fvgbfvfl4hlhv/Astra13.jar.src.zip/file

3) The skidding begins

Exhibit A:

Class astra.settings.BooleanSetting

Resent 3.8:

image

Exhibit B:

Class astra.settings.BooleanSetting

image

This is not inherently skidded, however, if you take a closer look, you come to see that BooleanSetting#enabled is a
static field. This shows about how much attention was given to the class when it was written (or skidded, but probably refactored.)

Exhibit C:

Package astra.ui.auth.openauth

Two things wrong here.

One, what is this package naming. Two, openauth is licensed under GPL-3.0. In distributing Astra Client, without
distributing said source, Astra Client is in direct violation of the GPL-3.0 license.

Two, for some reason, they have duplicated this package into astra.util.openauth. "Why?" is beyond me.

Exhibit D:

Package astra.util.font

Sub-exhibit 1:

Class astra.util.font.CFont

imageimageimage

Augustus 2.6:

imageimageimage

I would like to make a remark here.

The reason the Eaglercraft version of Astra Client does not have a custom font renderer is because

A) Eaglercraft runs on WebGL 2.0, which is based on OpenGLES 3.0 and

B) Augustus 2.6's CFont is programmed for OpenGL 1.1 (shown by the GL11# calls.) CFont uses the Fixed Function
Pipeline (glVertex* and glTexCoord calls) to render, whereas in OpenGLES 3.0, the Fixed Function Pipeline is
deprecated, and one must rewrite CFont to use the Shader Function Pipeline (which Astra Client has very obviously not done.)

Sub-exhibit 2:

Not exactly skidding, but a very interesting and roundabout way to load fonts indeed.

Method astra.util.font.FontUtils#bootstrap

image

Astute readers may see that this is a smart way to load all 3 sizes of the font at the same time iff
(if and only if) getFont was a blocking function. However, that is not the case. In fact, the unnecessary calls to
creating a new thread probably cause more performance deterioration when the JVM has to synchronize and create new threads.

The astute reader may incorrectly deduce that Font#createFont is actually a function that takes quite a long time to
run, so therefore getFont is a blocking function. However, this is only because Astra Client most likely skidded
this from somewhere, in which the slow method Font#createFont(int, InputStream) is used. It is sufficient to get
around this bottleneck in the getFont call by invoking Font#createFont(int, File) instead. Benchmark will be provided below.

Method astra.util.font.FontUtils#getFont

image

Two things to note.
One, the astute reader may have already recognized that the Map#containsKey call is in direct violation of
lax1dude's Eaglercraft code standards. See section 2 of EAGLERCRAFT_CODE_STANDARDS.md, which is located in the official Eaglercraft repository.

Two, what is the point of the basically empty new Thread() call (3rd new Thread call, the thread has no actual code inside)?

Benchmark code:

package astraclientbenchmarks.randomstuff;  

import org.openjdk.jmh.annotations.*;  

import java.awt.*;  
import java.io.File;  
import java.io.FileInputStream;  
import java.io.IOException;  
import java.io.InputStream;  
import java.util.HashMap;  
import java.util.Map;  
import java.util.concurrent.TimeUnit;  

@BenchmarkMode(Mode.AverageTime)  
@OutputTimeUnit(TimeUnit.MILLISECONDS)  
@State(Scope.Benchmark)  
@Fork(value = 1)  
@Warmup(iterations = 5, timeUnit = TimeUnit.MILLISECONDS, time = 5000)  
@Measurement(iterations = 5, timeUnit = TimeUnit.MILLISECONDS, time = 5000)  
@SuppressWarnings("unused")  
public class AstraClientCodeBenchmark {  
    public static void main(String[] args) throws IOException {  
        org.openjdk.jmh.Main.main(args);  
    }  

    @SuppressWarnings({"unused", "UnusedAssignment"})  
    @Benchmark  
    @BenchmarkMode(Mode.AverageTime)  
    public void testProperCode() {  
        Font normal_, large_, medium_;  
        FontUtilMock.MinecraftFontRendererMock normal, large, medium;  

        Map<String, Font> locationMap1 = new HashMap<>();  
        normal_ = FontUtilMock.getFont(locationMap1, "src/test/resources/Poppins-Medium.ttf", 19, true);  
        Map<String, Font> locationMap2 = new HashMap<>();  
        large_ = FontUtilMock.getFont(locationMap2, "src/test/resources/Poppins-Medium.ttf", 45, true);  
        Map<String, Font> locationMap3 = new HashMap<>();  
        medium_ = FontUtilMock.getFont(locationMap3, "src/test/resources/Poppins-Medium.ttf", 30, true);  

        normal = new FontUtilMock.MinecraftFontRendererMock(normal_, true, true);  
        large = new FontUtilMock.MinecraftFontRendererMock(large_, true, true);  
        medium = new FontUtilMock.MinecraftFontRendererMock(medium_, true, true);  
    }  

    @Benchmark  
    @BenchmarkMode(Mode.AverageTime)  
    public void testShittyCode() {  
        FontUtilMock.bootstrap();  
    }  

    public static class FontUtilMock {  
        public static volatile int completed;  
        public static MinecraftFontRendererMock normal;  
        public static MinecraftFontRendererMock large;  
        public static MinecraftFontRendererMock medium;  
        private static Font normal_;  
        private static Font large_;  
        private static Font medium_;  

        public FontUtilMock() {  
        }  

        private static Font getFont(Map<String, Font> locationMap, String location, int size) {  
            return getFont(locationMap, location, size, false);  
        }  

        private static Font getFont(Map<String, Font> locationMap, String location, int size, boolean efficient) {  
            Font font = null;  

            try {  
                if (locationMap.containsKey(location)) {  
                    font = ((Font)locationMap.get(location)).deriveFont(0, (float)size);  
                } else {  
                    if (efficient) {  
                        font = Font.createFont(0, new File(location));  
                    } else {  
//                        InputStream is = Minecraft.getMinecraft().getResourceManager().getResource(new ResourceLocation("Astra/font/" + location)).getInputStream();  
  InputStream is = new FileInputStream(location);  
                        font = Font.createFont(0, is);  
                    }  

                    locationMap.put(location, font);  
                    font = font.deriveFont(0, (float) size);  
                }  
            } catch (Exception var5) {  
                Exception e = var5;  
                e.printStackTrace();  
                System.out.println("Error loading font");  
                font = new Font("default", 0, 10);  
            }  

            return font;  
        }  

        public static boolean hasLoaded() {  
            return completed >= 3;  
        }  

        public static void bootstrap() {  
            (new Thread(() -> {  
                Map<String, Font> locationMap = new HashMap();  
                normal_ = getFont(locationMap, "src/test/resources/Poppins-Medium.ttf", 19);  
                ++completed;  
            })).start();  
            (new Thread(() -> {  
                Map<String, Font> locationMap = new HashMap();  
                large_ = getFont(locationMap, "src/test/resources/Poppins-Medium.ttf", 45);  
                ++completed;  
            })).start();  
            (new Thread(() -> {  
                new HashMap();  
                ++completed;  
            })).start();  
            (new Thread(() -> {  
                Map<String, Font> locationMap = new HashMap();  
                medium_ = getFont(locationMap, "src/test/resources/Poppins-Medium.ttf", 30);  
                ++completed;  
            })).start();  

            while(!hasLoaded()) {  
                try {  
                    Thread.sleep(5L);  
                } catch (InterruptedException var1) {  
                    InterruptedException e = var1;  
                    e.printStackTrace();  
                }  
            }  

            normal = new MinecraftFontRendererMock(normal_, true, true);  
            large = new MinecraftFontRendererMock(large_, true, true);  
            medium = new MinecraftFontRendererMock(medium_, true, true);  
        }  

        public static class MinecraftFontRendererMock {  
            public MinecraftFontRendererMock(Font font, boolean bl1, boolean bl2) {}  
        }  
    }  
}

Benchmark results:

# Benchmark mode: Average time, time/op
# Benchmark: astraclientbenchmarks.AstraClientCodeBenchmark.testProperCode

# Run progress: 0.00% complete, ETA 00:01:40
# Fork: 1 of 1
# Warmup Iteration   1: 0.147 ms/op
# Warmup Iteration   2: 0.140 ms/op
# Warmup Iteration   3: 0.137 ms/op
# Warmup Iteration   4: 0.136 ms/op
# Warmup Iteration   5: 0.136 ms/op
Iteration   1: 0.135 ms/op
Iteration   2: 0.144 ms/op
Iteration   3: 0.150 ms/op
Iteration   4: 0.142 ms/op
Iteration   5: 0.139 ms/op





# Benchmark mode: Average time, time/op
# Benchmark: astraclientbenchmarks.AstraClientCodeBenchmark.testShittyCode

# Run progress: 50.00% complete, ETA 00:00:52
# Fork: 1 of 1
# Warmup Iteration   1: 1.058 ms/op
# Warmup Iteration   2: 2.965 ms/op
# Warmup Iteration   3: 3.175 ms/op
# Warmup Iteration   4: 5.048 ms/op
# Warmup Iteration   5: 4.942 ms/op
Iteration   1: 5.433 ms/op
Iteration   2: 10.476 ms/op
Iteration   3: 4.936 ms/op
Iteration   4: 16.053 ms/op
Iteration   5: 28.110 ms/op
Sub-exhibit 3

How does one skid the entire CFont class and mess this up:

image

Exhibit E:

Class astra.util.RoundedUtils

Method #drawSmoothRoundedRect

image

Resent 3.8:

image

Astra didn't even bother changing the names. If you're wondering why the Resent one looks different, that is because the
compiler produced brackets in the Astra decompilation whereas the Resent source does not contain brackets.

In fact, the entire class is basically all skidded from Resent 3.8, one can compare the sources if they want. I only
showed 1 method in this exhibit, as otherwise it would take too much space.

Exhibit F:

Class astra.Rainbow

Method #drawChromaString

image

Resent 3.8:

image

Astra didn't even bother changing the variable names.

Exhibit G:

Class astra.betterfps.BetterFpsHelper

Sub-exhibit 1:

Method #loadConfig

image

I didn't know Astra Client and SeaClient were spelled the same.......

Did I forget to mention that Astra's resources are in Astra/*?

Sub-exhibit 2:

image

Once again, "SeaClient" of all things.

Exhibit H:

Package astra.cosmetics

Sub-exhibit 1:

Class astra.cosmetics.CosmeticDragonWings$ModelDragonWings

Method #render

image

Quick google search of BLueDD lets us know that BLueDD is the developer of SeaClient.

Sub-exhibit 2:

Class astra.cosmetics.CapeChecker

image

Point of this? Makes no sense when you think about it deeper. Why Username? What's so special about that?

Also, the code is pretty bad, could be written more cleanly.

Sub-exhibit 3:

Class astra.cosmetics.CapeRenderer

image

From "your_mod_id", one can conclude that this class was most likely skidded from a Fabric/Forge mod/tutorial on how to make capes.

Exhibit I:

Package astra.hud.mod.impl

Sub-exhibit 1:

Class astra.hud.mod.impl.AutoGG

Method #handleChat

image

BlocksMC on Eaglercraft, checks out.

Sub-exhibit 2:

Class astra.hud.mod.impl.ModPotionStatus

Method #draw

image

Resent 3.8:

image

Note that the java compiler has optimized the i2 += l operation from the end of the loop into the update expression
of the for loop.

The local variable names match between Resent 3.8 and Astra Client, which shows that Astra Client's ModPotionStatus was
a copy and paste, plus modifications.

4) Closing

There is probably more that I missed in this quick look at Astra Client.

Edit

Pub: 05 Feb 2025 03:05 UTC

Views: 69