From 1c790e5f2ea6f792139060a313a92d8993d4aae1 Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Mon, 10 Nov 2025 18:28:45 +0100 Subject: [PATCH 1/4] Transition to classgraph lib for reflections TODO: see if the code can be improved further --- build.gradle.kts | 2 +- gradle/libs.versions.toml | 6 +-- .../meteorclient/addons/GithubRepo.java | 3 +- .../meteorclient/systems/friends/Friend.java | 3 +- .../meteorclient/utils/ReflectInit.java | 36 +++++++++++----- .../utils/entity/TargetUtils.java | 2 +- .../utils/network/FailedHttpResponse.java | 3 +- .../meteorclient/utils/network/Http.java | 2 +- .../utils/network/PacketUtilsUtil.java | 43 +++++++++++-------- 9 files changed, 61 insertions(+), 39 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 89b98d77d3..3ad5f8d025 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -95,7 +95,7 @@ dependencies { jij(libs.orbit) jij(libs.starscript) jij(libs.discord.ipc) - jij(libs.reflections) + jij(libs.classgraph) jij(libs.netty.handler.proxy) { isTransitive = false } jij(libs.netty.codec.socks) { isTransitive = false } jij(libs.waybackauthlib) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 6f9df24c6f..733ce4e7eb 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -26,8 +26,8 @@ orbit = "0.2.4" starscript = "0.2.5" # DiscordRPC (https://github.com/MeteorDevelopment/java-discord-rpc) discordipc = "1.1" -# Reflections (https://github.com/ronmamo/reflections) -reflections = "0.10.2" +# Classgraph (https://github.com/classgraph/classgraph) +classgraph = "4.8.184" # Netty (https://github.com/netty/netty) netty = "4.2.7.Final" # ViaFabricPlus (https://github.com/ViaVersion/ViaFabricPlus) @@ -54,7 +54,7 @@ viafabricplus-api = { module = "com.viaversion:viafabricplus-api", version.ref = orbit = { module = "meteordevelopment:orbit", version.ref = "orbit" } starscript = { module = "org.meteordev:starscript", version.ref = "starscript" } discord-ipc = { module = "meteordevelopment:discord-ipc", version.ref = "discordipc" } -reflections = { module = "org.reflections:reflections", version.ref = "reflections" } +classgraph = { module = "io.github.classgraph:classgraph", version.ref = "classgraph" } netty-handler-proxy = { module = "io.netty:netty-handler-proxy", version.ref = "netty" } netty-codec-socks = { module = "io.netty:netty-codec-socks", version.ref = "netty" } waybackauthlib = { module = "de.florianmichael:WaybackAuthLib", version.ref = "waybackauthlib" } diff --git a/src/main/java/meteordevelopment/meteorclient/addons/GithubRepo.java b/src/main/java/meteordevelopment/meteorclient/addons/GithubRepo.java index 0ee4158f9f..57a4036b0d 100644 --- a/src/main/java/meteordevelopment/meteorclient/addons/GithubRepo.java +++ b/src/main/java/meteordevelopment/meteorclient/addons/GithubRepo.java @@ -6,8 +6,7 @@ package meteordevelopment.meteorclient.addons; import meteordevelopment.meteorclient.utils.network.Http; - -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; public record GithubRepo(String owner, String name, String branch, @Nullable String accessToken) { public GithubRepo(String owner, String name, @Nullable String accessToken) { diff --git a/src/main/java/meteordevelopment/meteorclient/systems/friends/Friend.java b/src/main/java/meteordevelopment/meteorclient/systems/friends/Friend.java index 125479962d..9a2f1b3b13 100644 --- a/src/main/java/meteordevelopment/meteorclient/systems/friends/Friend.java +++ b/src/main/java/meteordevelopment/meteorclient/systems/friends/Friend.java @@ -15,8 +15,8 @@ import net.minecraft.entity.player.PlayerEntity; import net.minecraft.nbt.NbtCompound; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.net.http.HttpResponse; import java.util.Objects; import java.util.UUID; @@ -38,6 +38,7 @@ public Friend(String name, @Nullable UUID id) { public Friend(PlayerEntity player) { this(player.getName().getString(), player.getUuid()); } + public Friend(String name) { this(name, null); } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java b/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java index ac4ca4cd06..9c8d359809 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java @@ -5,10 +5,11 @@ package meteordevelopment.meteorclient.utils; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.MethodInfo; +import io.github.classgraph.ScanResult; import meteordevelopment.meteorclient.addons.AddonManager; import meteordevelopment.meteorclient.addons.MeteorAddon; -import org.reflections.Reflections; -import org.reflections.scanners.Scanners; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; @@ -17,7 +18,7 @@ import java.util.stream.Collectors; public class ReflectInit { - private static final List reflections = new ArrayList<>(); + private static final List packages = new ArrayList<>(); private ReflectInit() { } @@ -35,19 +36,32 @@ public static void registerPackages() { private static void add(MeteorAddon addon) { String pkg = addon.getPackage(); if (pkg == null || pkg.isBlank()) return; - reflections.add(new Reflections(pkg, Scanners.MethodsAnnotated)); + packages.add(pkg); } public static void init(Class annotation) { - for (Reflections reflection : reflections) { - Set initTasks = reflection.getMethodsAnnotatedWith(annotation); - if (initTasks == null) return; + for (String pkg : packages) { + try (ScanResult scanResult = new ClassGraph() + .acceptPackages(pkg) + .enableMethodInfo() + .enableAnnotationInfo() + .scan()) { - Map, List> byClass = initTasks.stream().collect(Collectors.groupingBy(Method::getDeclaringClass)); - Set left = new HashSet<>(initTasks); + Set initTasks = scanResult.getClassesWithMethodAnnotation(annotation) + .stream() + .flatMap(classInfo -> classInfo.getMethodInfo().stream()) + .filter(methodInfo -> methodInfo.hasAnnotation(annotation)) + .map(MethodInfo::loadClassAndGetMethod) + .collect(Collectors.toSet()); - for (Method m; (m = left.stream().findAny().orElse(null)) != null; ) { - reflectInit(m, annotation, left, byClass); + if (initTasks.isEmpty()) continue; + + Map, List> byClass = initTasks.stream().collect(Collectors.groupingBy(Method::getDeclaringClass)); + Set left = new HashSet<>(initTasks); + + for (Method m; (m = left.stream().findAny().orElse(null)) != null; ) { + reflectInit(m, annotation, left, byClass); + } } } } diff --git a/src/main/java/meteordevelopment/meteorclient/utils/entity/TargetUtils.java b/src/main/java/meteordevelopment/meteorclient/utils/entity/TargetUtils.java index bf852e0823..92f9e2b00a 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/entity/TargetUtils.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/entity/TargetUtils.java @@ -13,8 +13,8 @@ import net.minecraft.entity.Entity; import net.minecraft.entity.player.PlayerEntity; import net.minecraft.world.GameMode; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.util.ArrayList; import java.util.List; import java.util.function.Predicate; diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/FailedHttpResponse.java b/src/main/java/meteordevelopment/meteorclient/utils/network/FailedHttpResponse.java index c108179288..202989d31f 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/FailedHttpResponse.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/FailedHttpResponse.java @@ -5,7 +5,8 @@ package meteordevelopment.meteorclient.utils.network; -import javax.annotation.Nullable; +import org.jetbrains.annotations.Nullable; + import javax.net.ssl.SSLSession; import java.net.URI; import java.net.http.HttpClient; diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/Http.java b/src/main/java/meteordevelopment/meteorclient/utils/network/Http.java index 9245689f6f..7dfdf30373 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/Http.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/Http.java @@ -8,8 +8,8 @@ import com.google.gson.Gson; import com.google.gson.GsonBuilder; import meteordevelopment.meteorclient.utils.other.JsonDateDeserializer; +import org.jetbrains.annotations.Nullable; -import javax.annotation.Nullable; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Type; diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java index c82981f035..d8f9f8513a 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java @@ -5,21 +5,22 @@ package meteordevelopment.meteorclient.utils.network; +import io.github.classgraph.ClassGraph; +import io.github.classgraph.ClassInfo; +import io.github.classgraph.ScanResult; import net.minecraft.network.packet.BundlePacket; import net.minecraft.network.packet.BundleSplitterPacket; import net.minecraft.network.packet.Packet; -import org.reflections.Reflections; -import org.reflections.scanners.Scanners; import java.io.BufferedWriter; import java.io.File; import java.io.FileWriter; import java.io.IOException; import java.util.Comparator; -import java.util.Set; import java.util.SortedSet; import java.util.TreeSet; import java.util.function.Predicate; +import java.util.stream.Collectors; public class PacketUtilsUtil { private PacketUtilsUtil() { @@ -114,26 +115,32 @@ public static void init() throws IOException { } } - @SuppressWarnings("rawtypes") private static void processPackets(BufferedWriter writer, String packageName, String packetMapName, String reverseMapName, Predicate> exclusionFilter) throws IOException { Comparator> packetsComparator = Comparator .comparing((Class cls) -> cls.getName().substring(cls.getName().lastIndexOf('.') + 1)) .thenComparing(Class::getName); - Reflections reflections = new Reflections(packageName, Scanners.SubTypes); - Set> packets = reflections.getSubTypesOf(Packet.class); - SortedSet> sortedPackets = new TreeSet<>(packetsComparator); - sortedPackets.addAll(packets); - - for (Class packet : sortedPackets) { - if (exclusionFilter.test(packet)) continue; - - String name = packet.getName(); - String className = name.substring(name.lastIndexOf('.') + 1).replace('$', '.'); - String fullName = name.replace('$', '.'); - - writer.write(" %s.put(%s.class, \"%s\");%n".formatted(packetMapName, fullName, className)); - writer.write(" %s.put(\"%s\", %s.class);%n".formatted(reverseMapName, className, fullName)); + try (ScanResult scanResult = new ClassGraph() + .acceptPackages(packageName) + .enableClassInfo() + .ignoreClassVisibility() + .scan()) { + + SortedSet> sortedPackets = scanResult + .getClassesImplementing(Packet.class) + .stream() + .map(ClassInfo::loadClass) + .filter(exclusionFilter.negate()) + .collect(Collectors.toCollection(() -> new TreeSet<>(packetsComparator))); + + for (Class packet : sortedPackets) { + String name = packet.getName(); + String className = name.substring(name.lastIndexOf('.') + 1).replace('$', '.'); + String fullName = name.replace('$', '.'); + + writer.write(" %s.put(%s.class, \"%s\");%n".formatted(packetMapName, fullName, className)); + writer.write(" %s.put(\"%s\", %s.class);%n".formatted(reverseMapName, className, fullName)); + } } } } From 4b3b60366d89c3f2f9217232a54115bc90d9a27e Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:08:37 +0100 Subject: [PATCH 2/4] Use Java NIO where possible, write file at once NOTE: var is used to suppress a comp-time warning of `toList()` --- .../utils/network/PacketUtilsUtil.java | 183 +++++++++--------- 1 file changed, 92 insertions(+), 91 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java index d8f9f8513a..2afc284d07 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java @@ -12,17 +12,15 @@ import net.minecraft.network.packet.BundleSplitterPacket; import net.minecraft.network.packet.Packet; -import java.io.BufferedWriter; import java.io.File; -import java.io.FileWriter; import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.*; import java.util.Comparator; -import java.util.SortedSet; -import java.util.TreeSet; +import java.util.List; import java.util.function.Predicate; -import java.util.stream.Collectors; -public class PacketUtilsUtil { +public final class PacketUtilsUtil { private PacketUtilsUtil() { } @@ -35,112 +33,115 @@ public static void main(String[] args) { } public static void init() throws IOException { - // Generate PacketUtils.java - File file = new File("src/main/java/%s/PacketUtils.java".formatted(PacketUtilsUtil.class.getPackageName().replace('.', '/'))); - if (!file.exists()) { - file.getParentFile().mkdirs(); - file.createNewFile(); - } + // Target path + Path filePath = Path.of( + "src", "main", "java", + PacketUtilsUtil.class.getPackageName().replace('.', File.separatorChar), + "PacketUtils.java" + ); + + Files.createDirectories(filePath.getParent()); + + // Generate mappings + String c2sMappings = processPackets("net.minecraft.network.packet.c2s", "C2S_PACKETS", "C2S_PACKETS_R", + packet -> false + ); + String s2cMappings = processPackets("net.minecraft.network.packet.s2c", "S2C_PACKETS", "S2C_PACKETS_R", + packet -> BundlePacket.class.isAssignableFrom(packet) || BundleSplitterPacket.class.isAssignableFrom(packet) + ); + + // Write to file + String content = """ + /* + * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client/). + * Copyright (c) Meteor Development. + */ + + package meteordevelopment.meteorclient.utils.network; + + import com.google.common.collect.Sets; + import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap; + import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap; + import net.minecraft.network.packet.Packet; + + import java.util.Map; + import java.util.Set; + + public class PacketUtils { + private static final Map>, String> S2C_PACKETS = new Reference2ObjectOpenHashMap<>(); + private static final Map>, String> C2S_PACKETS = new Reference2ObjectOpenHashMap<>(); + + private static final Map>> S2C_PACKETS_R = new Object2ReferenceOpenHashMap<>(); + private static final Map>> C2S_PACKETS_R = new Object2ReferenceOpenHashMap<>(); + + public static final Set>> PACKETS = Sets.union(getC2SPackets(), getS2CPackets()); + + static { + %s + + %s + } + + private PacketUtils() { + } + + public static String getName(Class> packetClass) { + String name = S2C_PACKETS.get(packetClass); + if (name != null) return name; + return C2S_PACKETS.get(packetClass); + } + + public static Class> getPacket(String name) { + Class> packet = S2C_PACKETS_R.get(name); + if (packet != null) return packet; + return C2S_PACKETS_R.get(name); + } + + public static Set>> getS2CPackets() { + return S2C_PACKETS.keySet(); + } + + public static Set>> getC2SPackets() { + return C2S_PACKETS.keySet(); + } + } + """.formatted( + c2sMappings.indent(8).stripTrailing(), + s2cMappings.indent(8).stripTrailing() + ); - try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) { - writer.write("/*\n"); - writer.write(" * This file is part of the Meteor Client distribution (https://github.com/MeteorDevelopment/meteor-client/).\n"); - writer.write(" * Copyright (c) Meteor Development.\n"); - writer.write(" */\n\n"); - - writer.write("package meteordevelopment.meteorclient.utils.network;\n\n"); - - // Write imports - writer.write("import com.google.common.collect.Sets;\n"); - writer.write("import it.unimi.dsi.fastutil.objects.Object2ReferenceOpenHashMap;\n"); - writer.write("import it.unimi.dsi.fastutil.objects.Reference2ObjectOpenHashMap;\n"); - writer.write("import net.minecraft.network.packet.Packet;\n\n"); - - writer.write("import java.util.Map;\n"); - writer.write("import java.util.Set;\n"); - - // Write class - writer.write("\npublic class PacketUtils {\n"); - - // Write fields - writer.write(" private static final Map>, String> S2C_PACKETS = new Reference2ObjectOpenHashMap<>();\n"); - writer.write(" private static final Map>, String> C2S_PACKETS = new Reference2ObjectOpenHashMap<>();\n\n"); - writer.write(" private static final Map>> S2C_PACKETS_R = new Object2ReferenceOpenHashMap<>();\n"); - writer.write(" private static final Map>> C2S_PACKETS_R = new Object2ReferenceOpenHashMap<>();\n\n"); - writer.write(" public static final Set>> PACKETS = Sets.union(getC2SPackets(), getS2CPackets());\n\n"); - - // Write static block - writer.write(" static {\n"); - - // Process packets - processPackets(writer, "net.minecraft.network.packet.c2s", "C2S_PACKETS", "C2S_PACKETS_R", - packet -> false // No exclusions for C2S packets - ); - writer.newLine(); - processPackets(writer, "net.minecraft.network.packet.s2c", "S2C_PACKETS", "S2C_PACKETS_R", - packet -> BundlePacket.class.isAssignableFrom(packet) || BundleSplitterPacket.class.isAssignableFrom(packet) - ); - - writer.write(" }\n\n"); - - writer.write(" private PacketUtils() {\n"); - writer.write(" }\n\n"); - - // Write getName method - writer.write(" public static String getName(Class> packetClass) {\n"); - writer.write(" String name = S2C_PACKETS.get(packetClass);\n"); - writer.write(" if (name != null) return name;\n"); - writer.write(" return C2S_PACKETS.get(packetClass);\n"); - writer.write(" }\n\n"); - - // Write getPacket method - writer.write(" public static Class> getPacket(String name) {\n"); - writer.write(" Class> packet = S2C_PACKETS_R.get(name);\n"); - writer.write(" if (packet != null) return packet;\n"); - writer.write(" return C2S_PACKETS_R.get(name);\n"); - writer.write(" }\n\n"); - - // Write getS2CPackets method - writer.write(" public static Set>> getS2CPackets() {\n"); - writer.write(" return S2C_PACKETS.keySet();\n"); - writer.write(" }\n\n"); - - // Write getC2SPackets method - writer.write(" public static Set>> getC2SPackets() {\n"); - writer.write(" return C2S_PACKETS.keySet();\n"); - writer.write(" }\n"); - - // Write end class - writer.write("}\n"); - } + Files.writeString(filePath, content, StandardCharsets.UTF_8); } - private static void processPackets(BufferedWriter writer, String packageName, String packetMapName, String reverseMapName, Predicate> exclusionFilter) throws IOException { + private static String processPackets(String packageName, String packetMapName, String reverseMapName, Predicate> exclusionFilter) { Comparator> packetsComparator = Comparator .comparing((Class cls) -> cls.getName().substring(cls.getName().lastIndexOf('.') + 1)) .thenComparing(Class::getName); + StringBuilder mappings = new StringBuilder(8192); + try (ScanResult scanResult = new ClassGraph() .acceptPackages(packageName) .enableClassInfo() .ignoreClassVisibility() .scan()) { - SortedSet> sortedPackets = scanResult - .getClassesImplementing(Packet.class) - .stream() + var packets = scanResult.getClassesImplementing(Packet.class).stream() .map(ClassInfo::loadClass) .filter(exclusionFilter.negate()) - .collect(Collectors.toCollection(() -> new TreeSet<>(packetsComparator))); + .sorted(packetsComparator) + .toList(); - for (Class packet : sortedPackets) { + for (Class packet : packets) { String name = packet.getName(); String className = name.substring(name.lastIndexOf('.') + 1).replace('$', '.'); String fullName = name.replace('$', '.'); - writer.write(" %s.put(%s.class, \"%s\");%n".formatted(packetMapName, fullName, className)); - writer.write(" %s.put(\"%s\", %s.class);%n".formatted(reverseMapName, className, fullName)); + mappings.append("%s.put(%s.class, \"%s\");%n".formatted(packetMapName, fullName, className)); + mappings.append("%s.put(\"%s\", %s.class);%n".formatted(reverseMapName, className, fullName)); } } + + return mappings.toString(); } } From aa97f7b6ca92daec00e9a30654daa0d7629e1e3b Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Tue, 11 Nov 2025 13:23:57 +0100 Subject: [PATCH 3/4] Use Stream directly Removes the intermediate Comparator, StringBuilder and packets list --- .../utils/network/PacketUtilsUtil.java | 35 ++++++++----------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java index 2afc284d07..851b4dd307 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/network/PacketUtilsUtil.java @@ -17,8 +17,8 @@ import java.nio.charset.StandardCharsets; import java.nio.file.*; import java.util.Comparator; -import java.util.List; import java.util.function.Predicate; +import java.util.stream.Collectors; public final class PacketUtilsUtil { private PacketUtilsUtil() { @@ -114,34 +114,27 @@ public static Set>> getC2SPackets() { } private static String processPackets(String packageName, String packetMapName, String reverseMapName, Predicate> exclusionFilter) { - Comparator> packetsComparator = Comparator - .comparing((Class cls) -> cls.getName().substring(cls.getName().lastIndexOf('.') + 1)) - .thenComparing(Class::getName); - - StringBuilder mappings = new StringBuilder(8192); - try (ScanResult scanResult = new ClassGraph() .acceptPackages(packageName) .enableClassInfo() .ignoreClassVisibility() .scan()) { - var packets = scanResult.getClassesImplementing(Packet.class).stream() + return scanResult.getClassesImplementing(Packet.class).stream() .map(ClassInfo::loadClass) .filter(exclusionFilter.negate()) - .sorted(packetsComparator) - .toList(); - - for (Class packet : packets) { - String name = packet.getName(); - String className = name.substring(name.lastIndexOf('.') + 1).replace('$', '.'); - String fullName = name.replace('$', '.'); - - mappings.append("%s.put(%s.class, \"%s\");%n".formatted(packetMapName, fullName, className)); - mappings.append("%s.put(\"%s\", %s.class);%n".formatted(reverseMapName, className, fullName)); - } + .sorted(Comparator + .comparing((Class cls) -> cls.getName().substring(cls.getName().lastIndexOf('.') + 1)) + .thenComparing(Class::getName) + ) + .map(packet -> { + String name = packet.getName(); + String className = name.substring(name.lastIndexOf('.') + 1).replace('$', '.'); + String fullName = name.replace('$', '.'); + return "%s.put(%s.class, \"%s\");%n%s.put(\"%s\", %s.class);" + .formatted(packetMapName, fullName, className, reverseMapName, className, fullName); + }) + .collect(Collectors.joining("\n")); } - - return mappings.toString(); } } From 51bd348f5b40c75924755f86629f2c37603c4a3e Mon Sep 17 00:00:00 2001 From: Big-Iron-Cheems <52252627+Big-Iron-Cheems@users.noreply.github.com> Date: Wed, 17 Dec 2025 15:11:02 +0100 Subject: [PATCH 4/4] Fix duplicate method execution in ReflectInit Deduplicate @PostInit/@PreInit methods across all package scans before execution. Methods were executed multiple times when the same class appeared in multiple addon package scans. Fixes NPE in TexturePacker caused by GuiRenderer.init() running twice. --- .../meteorclient/utils/ReflectInit.java | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java b/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java index 9c8d359809..3770249a8e 100644 --- a/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java +++ b/src/main/java/meteordevelopment/meteorclient/utils/ReflectInit.java @@ -40,6 +40,8 @@ private static void add(MeteorAddon addon) { } public static void init(Class annotation) { + Set allInitTasks = new HashSet<>(); + for (String pkg : packages) { try (ScanResult scanResult = new ClassGraph() .acceptPackages(pkg) @@ -54,15 +56,17 @@ public static void init(Class annotation) { .map(MethodInfo::loadClassAndGetMethod) .collect(Collectors.toSet()); - if (initTasks.isEmpty()) continue; + allInitTasks.addAll(initTasks); + } + } - Map, List> byClass = initTasks.stream().collect(Collectors.groupingBy(Method::getDeclaringClass)); - Set left = new HashSet<>(initTasks); + if (allInitTasks.isEmpty()) return; - for (Method m; (m = left.stream().findAny().orElse(null)) != null; ) { - reflectInit(m, annotation, left, byClass); - } - } + Map, List> byClass = allInitTasks.stream().collect(Collectors.groupingBy(Method::getDeclaringClass)); + Set left = new HashSet<>(allInitTasks); + + for (Method m; (m = left.stream().findAny().orElse(null)) != null; ) { + reflectInit(m, annotation, left, byClass); } }