From 968c01f1851019829edfc782310fa63e04e31f60 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Mon, 5 Feb 2024 16:57:35 +0100 Subject: [PATCH 01/15] Add the clisten command --- build.gradle | 2 + .../clientcommands/ClientCommands.java | 4 + .../earthcomputer/clientcommands/Configs.java | 8 + .../clientcommands/MappingsHelper.java | 226 ++++++ .../clientcommands/PacketDumper.java | 701 ++++++++++++++++++ .../clientcommands/ReflectionUtils.java | 19 + .../clientcommands/UnsafeUtils.java | 34 + .../clientcommands/command/ListenCommand.java | 267 +++++++ .../MojmapPacketClassArgumentType.java | 68 ++ .../mixin/MixinClientConnection.java | 34 + .../assets/clientcommands/lang/en_us.json | 11 + src/main/resources/mixins.clientcommands.json | 1 + 12 files changed, 1375 insertions(+) create mode 100644 src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/PacketDumper.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java create mode 100644 src/main/java/net/earthcomputer/clientcommands/mixin/MixinClientConnection.java diff --git a/build.gradle b/build.gradle index e8569a04c..1d826d41b 100644 --- a/build.gradle +++ b/build.gradle @@ -52,6 +52,8 @@ dependencies { modRuntimeOnly('me.djtheredstoner:DevAuth-fabric:1.1.0') { exclude group: 'net.fabricmc', module: 'fabric-loader' } + + include api('net.fabricmc:mapping-io:0.5.1') } jar { diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index e6e5fb338..a62f10c98 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -14,6 +14,7 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.MinecraftVersion; import net.minecraft.client.MinecraftClient; import net.minecraft.command.CommandRegistryAccess; import net.minecraft.network.PacketByteBuf; @@ -85,6 +86,8 @@ public void onInitializeClient() { new ModConfigBuilder("clientcommands", Configs.class).build(); ItemGroupCommand.registerItemGroups(); + + MappingsHelper.initMappings(MinecraftVersion.CURRENT.getName()); } private static Set getCommands(CommandDispatcher dispatcher) { @@ -160,6 +163,7 @@ public static void registerCommands(CommandDispatcher CrackRNGCommand.register(dispatcher); WeatherCommand.register(dispatcher); PluginsCommand.register(dispatcher); + ListenCommand.register(dispatcher); Calendar calendar = Calendar.getInstance(); boolean registerChatCommand = calendar.get(Calendar.MONTH) == Calendar.APRIL && calendar.get(Calendar.DAY_OF_MONTH) == 1; diff --git a/src/main/java/net/earthcomputer/clientcommands/Configs.java b/src/main/java/net/earthcomputer/clientcommands/Configs.java index da095dda8..8fde47958 100644 --- a/src/main/java/net/earthcomputer/clientcommands/Configs.java +++ b/src/main/java/net/earthcomputer/clientcommands/Configs.java @@ -113,4 +113,12 @@ public static void setMaxChorusItemThrows(int maxChorusItemThrows) { public static boolean conditionLessThan1_20() { return MultiVersionCompat.INSTANCE.getProtocolVersion() < MultiVersionCompat.V1_20; } + + @Config + public static PacketDumpMethod packetDumpMethod = PacketDumpMethod.REFLECTION; + + public enum PacketDumpMethod { + REFLECTION, + BYTE_BUF, + } } diff --git a/src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java new file mode 100644 index 000000000..7dcabd89d --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java @@ -0,0 +1,226 @@ +package net.earthcomputer.clientcommands; + +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.mappingio.MappingReader; +import net.fabricmc.mappingio.format.MappingFormat; +import net.fabricmc.mappingio.tree.MappingTree; +import net.fabricmc.mappingio.tree.MemoryMappingTree; + +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringReader; +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.util.Collection; +import java.util.Optional; + +public class MappingsHelper { + + private static final MemoryMappingTree mojmapOfficial = new MemoryMappingTree(); + private static final int SRC_OFFICIAL = 0; + private static final int DEST_OFFICIAL = 0; + + private static final MemoryMappingTree officialIntermediaryNamed = new MemoryMappingTree(); + private static final int SRC_INTERMEDIARY = 0; + private static final int DEST_INTERMEDIARY = 0; + private static final int SRC_NAMED = 1; + private static final int DEST_NAMED = 1; + + private static final boolean IS_DEV_ENV = FabricLoader.getInstance().isDevelopmentEnvironment(); + + private static final HttpClient httpClient = HttpClient.newHttpClient(); + + public static void initMappings(String version) { + HttpRequest versionsRequest = HttpRequest.newBuilder() + .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(versionsBody -> { + JsonObject versionsJson = JsonParser.parseString(versionsBody).getAsJsonObject(); + String versionUrl = versionsJson.getAsJsonArray("versions").asList().stream() + .map(JsonElement::getAsJsonObject) + .filter(v -> v.get("id").getAsString().equals(version)) + .map(v -> v.get("url").getAsString()) + .findAny().orElseThrow(); + + HttpRequest versionRequest = HttpRequest.newBuilder() + .uri(URI.create(versionUrl)) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(versionBody -> { + JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); + String mappingsUrl = versionJson + .getAsJsonObject("downloads") + .getAsJsonObject("client_mappings") + .get("url").getAsString(); + + HttpRequest mappingsRequest = HttpRequest.newBuilder() + .uri(URI.create(mappingsUrl)) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenApply(StringReader::new) + .thenAccept(reader -> { + try { + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + }); + }); + + try (InputStream stream = FabricLoader.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny")) { + if (stream == null) { + throw new IOException("Could not find mappings.tiny"); + } + MappingReader.read(new InputStreamReader(stream), IS_DEV_ENV ? MappingFormat.TINY_2_FILE : MappingFormat.TINY_FILE, officialIntermediaryNamed); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static Collection mojmapClasses() { + return mojmapOfficial.getClasses(); + } + + public static Optional mojmapToOfficial_class(String mojmapClass) { + MappingTree.ClassMapping officialClass = mojmapOfficial.getClass(mojmapClass); + if (officialClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(officialClass.getDstName(DEST_OFFICIAL)); + } + + public static Optional officialToMojmap_class(String officialClass) { + MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass, SRC_OFFICIAL); + if (mojmapClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapClass.getSrcName()); + } + + public static Optional mojmapToNamed_class(String mojmapClass) { + Optional officialClass = mojmapToOfficial_class(mojmapClass); + if (officialClass.isEmpty()) { + return Optional.empty(); + } + MappingTree.ClassMapping namedClass = officialIntermediaryNamed.getClass(officialClass.get()); + if (namedClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(namedClass.getDstName(DEST_NAMED)); + } + + public static Optional namedToMojmap_class(String namedClass) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); + if (officialClass == null) { + return Optional.empty(); + } + MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass.getSrcName(), SRC_OFFICIAL); + if (mojmapClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapClass.getSrcName()); + } + + public static Optional mojmapToIntermediary_class(String mojmapClass) { + Optional officialClass = mojmapToOfficial_class(mojmapClass); + if (officialClass.isEmpty()) { + return Optional.empty(); + } + MappingTree.ClassMapping intermediaryClass = officialIntermediaryNamed.getClass(officialClass.get()); + if (intermediaryClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(intermediaryClass.getDstName(DEST_INTERMEDIARY)); + } + + public static Optional intermediaryToMojmap_class(String intermediaryClass) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); + if (officialClass == null) { + return Optional.empty(); + } + MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass.getSrcName(), SRC_OFFICIAL); + if (mojmapClass == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapClass.getSrcName()); + } + + public static Optional namedOrIntermediaryToMojmap_class(String namedOrIntermediaryClass) { + if (IS_DEV_ENV) { + return MappingsHelper.namedToMojmap_class(namedOrIntermediaryClass); + } + return MappingsHelper.intermediaryToMojmap_class(namedOrIntermediaryClass); + } + + public static Optional mojmapToNamedOrIntermediary_class(String mojmapClass) { + if (IS_DEV_ENV) { + return MappingsHelper.mojmapToNamed_class(mojmapClass); + } + return MappingsHelper.mojmapToIntermediary_class(mojmapClass); + } + + public static Optional officialToMojmap_field(String officialClass, String officialField) { + MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass, officialField, null); + if (mojmapField == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapField.getSrcName()); + } + + public static Optional namedToMojmap_field(String namedClass, String namedField) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); + if (officialClass == null) { + return Optional.empty(); + } + MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(namedClass, namedField, null, SRC_NAMED); + if (officialField == null) { + return Optional.empty(); + } + MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + if (mojmapField == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapField.getSrcName()); + } + + public static Optional intermediaryToMojmap_field(String intermediaryClass, String intermediaryField) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); + if (officialClass == null) { + return Optional.empty(); + } + MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(intermediaryClass, intermediaryField, null, SRC_INTERMEDIARY); + if (officialField == null) { + return Optional.empty(); + } + MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + if (mojmapField == null) { + return Optional.empty(); + } + return Optional.ofNullable(mojmapField.getSrcName()); + } + + public static Optional namedOrIntermediaryToMojmap_field(String namedOrIntermediaryClass, String namedOrIntermediaryField) { + if (IS_DEV_ENV) { + return namedToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); + } + return intermediaryToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/PacketDumper.java new file mode 100644 index 000000000..bd39eaca1 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/PacketDumper.java @@ -0,0 +1,701 @@ +package net.earthcomputer.clientcommands; + +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.stream.JsonWriter; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.authlib.yggdrasil.response.ProfileSearchResultsResponse; +import com.mojang.datafixers.util.Either; +import com.mojang.serialization.Codec; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; +import com.mojang.util.ByteBufferTypeAdapter; +import com.mojang.util.InstantTypeAdapter; +import com.mojang.util.UUIDTypeAdapter; +import io.netty.buffer.ByteBuf; +import io.netty.buffer.Unpooled; +import io.netty.handler.codec.EncoderException; +import it.unimi.dsi.fastutil.ints.IntList; +import net.minecraft.item.ItemStack; +import net.minecraft.nbt.NbtElement; +import net.minecraft.network.PacketByteBuf; +import net.minecraft.network.packet.Packet; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.text.Text; +import net.minecraft.util.Identifier; +import net.minecraft.util.Util; +import net.minecraft.util.collection.IndexedIterable; +import net.minecraft.util.hit.BlockHitResult; +import net.minecraft.util.math.*; +import org.apache.commons.io.function.IOBiConsumer; +import org.apache.commons.io.function.IORunnable; +import org.apache.commons.io.function.IOStream; +import org.apache.commons.io.function.Uncheck; +import org.jetbrains.annotations.Nullable; +import org.joml.Quaternionf; +import org.joml.Vector3f; + +import java.io.IOException; +import java.io.InputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.nio.channels.ScatteringByteChannel; +import java.nio.charset.Charset; +import java.security.PublicKey; +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.time.Instant; +import java.util.*; +import java.util.function.ToIntFunction; + +/** + * @author Gaming32 + */ +public class PacketDumper { + public static void dumpPacket(Packet packet, JsonWriter writer) throws IOException { + writer.beginArray(); + try { + packet.write(new PacketDumpByteBuf(writer)); + } catch (UncheckedIOException e) { + throw e.getCause(); + } + writer.endArray(); + } + + public static class PacketDumpByteBuf extends PacketByteBuf { + private static final Gson GSON = new GsonBuilder() + .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) + .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) + .registerTypeHierarchyAdapter(ByteBuffer.class, new ByteBufferTypeAdapter().nullSafe()) + .registerTypeAdapter(GameProfile.class, new GameProfile.Serializer()) + .registerTypeAdapter(PropertyMap.class, new PropertyMap.Serializer()) + .registerTypeAdapter(ProfileSearchResultsResponse.class, new ProfileSearchResultsResponse.Serializer()) + .create(); + private static final DateFormat ISO_8601 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm'Z'"); + + private final JsonWriter writer; + + public PacketDumpByteBuf(JsonWriter writer) { + super(Unpooled.buffer(0, 0)); + this.writer = writer; + } + + @Override + @SuppressWarnings("deprecation") + public PacketDumpByteBuf encode(DynamicOps ops, Codec codec, T value) { + return dump("withCodec", () -> { + dumpValueClass(value); + writer.name("value").value(Objects.toString(value)); + writer.name("encodedNbt").value(Util.getResult( + codec.encodeStart(ops, value), + message -> new EncoderException("Failed to encode: " + message + " " + value) + ).toString()); + writer.name("encodedJson"); + GSON.toJson(Util.getResult( + codec.encodeStart(JsonOps.INSTANCE, value), + message -> new EncoderException("Failed to encode: " + message + " " + value) + ), writer); + }); + } + + @Override + public void encodeAsJson(Codec codec, T value) { + dump("jsonWithCodec", () -> { + dumpValueClass(value); + writer.name("value").value(Objects.toString(value)); + writer.name("encodedJson"); + GSON.toJson(Util.getResult( + codec.encodeStart(JsonOps.INSTANCE, value), + message -> new EncoderException("Failed to encode: " + message + " " + value) + ), writer); + }); + } + + @Override + public void writeRegistryValue(IndexedIterable indexedIterable, T value) { + dump("id", () -> { + dumpValueClass(value); + writer.name("value").value(Objects.toString(value)); + if (indexedIterable instanceof Registry registry) { + writer.name("registry").value(registry.getKey().getValue().toString()); + writer.name("valueKey").value(Objects.toString(registry.getId(value))); + } + writer.name("id").value(indexedIterable.getRawId(value)); + }); + } + + @Override + public void writeRegistryEntry(IndexedIterable> idMap, RegistryEntry value, PacketWriter directWriter) { + dump("idHolder", () -> { + writer.name("kind").value(value.getType().name()); + value.getKeyOrValue().ifLeft(key -> Uncheck.run(() -> { + writer.name("referenceKey").value(key.getValue().toString()); + writer.name("id").value(idMap.getRawId(value)); + })).ifRight(directValue -> Uncheck.run(() -> { + writer.name("directValue"); + dumpValue(directValue, directWriter); + })); + }); + } + + @Override + public void writeCollection(Collection collection, PacketWriter elementWriter) { + dump("collection", () -> { + writer.name("size").value(collection.size()); + writer.name("elements").beginArray(); + for (T element : collection) { + dumpValue(element, elementWriter); + } + writer.endArray(); + }); + } + + @Override + public void writeIntList(IntList intIdList) { + dump("intIdList", () -> { + writer.name("size").value(intIdList.size()); + writer.name("elements").beginArray(); + for (int value : intIdList) { + writer.value(value); + } + writer.endArray(); + }); + } + + @Override + public void writeMap(Map map, PacketWriter keyWriter, PacketWriter valueWriter) { + dump("map", () -> { + writer.name("size").value(map.size()); + writer.name("elements").beginArray(); + for (var entry : map.entrySet()) { + writer.beginObject(); + writer.name("key"); + dumpValue(entry.getKey(), keyWriter); + writer.name("value"); + dumpValue(entry.getValue(), valueWriter); + writer.endObject(); + } + writer.endArray(); + }); + } + + @Override + public > void writeEnumSet(EnumSet enumSet, Class enumClass) { + dump("enumSet", () -> { + writer.name("enumClass").value(MappingsHelper.namedOrIntermediaryToMojmap_class(enumClass.getName().replace('.', '/')).orElseThrow()); + writer.name("size").value(enumSet.size()); + writer.name("elements").beginArray(); + for (E element : enumSet) { + writer.value(element.name()); + } + writer.endArray(); + }); + } + + @Override + public void writeOptional(Optional optional, PacketWriter valueWriter) { + writeNullable("optional", optional.orElse(null), valueWriter); + } + + @Override + public void writeNullable(@Nullable T value, PacketWriter writer) { + writeNullable("nullable", value, writer); + } + + private void writeNullable(String type, T value, PacketWriter valueWriter) { + dump(type, () -> { + writer.name("present"); + if (value != null) { + writer.value(true); + writer.name("value"); + dumpValue(value, valueWriter); + } else { + writer.value(false); + } + }); + } + + @Override + public void writeEither(Either value, PacketWriter leftWriter, PacketWriter rightWriter) { + dump("either", () -> { + writer.name("either"); + value.ifLeft(left -> Uncheck.run(() -> { + writer.value("left"); + writer.name("value"); + dumpValue(left, leftWriter); + })).ifRight(right -> Uncheck.run(() -> { + writer.value("right"); + writer.name("value"); + dumpValue(right, rightWriter); + })); + }); + } + + @Override + public PacketDumpByteBuf writeByteArray(byte[] array) { + return dump("byteArray", () -> writer + .name("length").value(array.length) + .name("value").value(Base64.getEncoder().encodeToString(array)) + ); + } + + @Override + public PacketDumpByteBuf writeIntArray(int[] array) { + return dump("varIntArray", () -> { + writer.name("length").value(array.length); + writer.name("elements").beginArray(); + for (int element : array) { + writer.value(element); + } + writer.endArray(); + }); + } + + @Override + public PacketDumpByteBuf writeLongArray(long[] array) { + return dump("longArray", () -> { + writer.name("length").value(array.length); + writer.name("elements").beginArray(); + for (long element : array) { + writer.value(element); + } + writer.endArray(); + }); + } + + @Override + public PacketDumpByteBuf writeBlockPos(BlockPos pos) { + return dump("blockPos", () -> writer + .name("x").value(pos.getX()) + .name("y").value(pos.getY()) + .name("z").value(pos.getZ()) + ); + } + + @Override + public PacketDumpByteBuf writeChunkPos(ChunkPos chunkPos) { + return dump("chunkPos", () -> writer + .name("x").value(chunkPos.x) + .name("z").value(chunkPos.z) + ); + } + + @Override + public PacketDumpByteBuf writeChunkSectionPos(ChunkSectionPos chunkSectionPos) { + return dump("sectionPos", () -> writer + .name("x").value(chunkSectionPos.getSectionX()) + .name("y").value(chunkSectionPos.getSectionY()) + .name("z").value(chunkSectionPos.getSectionZ()) + ); + } + + @Override + public void writeGlobalPos(GlobalPos pos) { + dump("globalPos", () -> writer + .name("level").value(pos.getDimension().getValue().toString()) + .name("x").value(pos.getPos().getX()) + .name("y").value(pos.getPos().getY()) + .name("z").value(pos.getPos().getZ()) + ); + } + + @Override + public void writeVector3f(Vector3f vector3f) { + dump("vector3f", () -> writer + .name("x").value(vector3f.x) + .name("y").value(vector3f.y) + .name("z").value(vector3f.z) + ); + } + + @Override + public void writeQuaternionf(Quaternionf quaternion) { + dump("quaternion", () -> writer + .name("x").value(quaternion.x) + .name("y").value(quaternion.y) + .name("z").value(quaternion.z) + .name("w").value(quaternion.w) + ); + } + + @Override + public void writeVec3d(Vec3d vec3) { + dump("vec3", () -> writer + .name("x").value(vec3.x) + .name("y").value(vec3.y) + .name("z").value(vec3.z) + ); + } + + @Override + public PacketDumpByteBuf writeText(Text text) { + return dump("component", () -> { + writer.name("value"); + GSON.toJson(Text.Serialization.toJsonTree(text), writer); + }); + } + + @Override + public PacketDumpByteBuf writeEnumConstant(Enum value) { + return dump("enum", () -> writer + .name("enum").value(value.getDeclaringClass().getName()) + .name("value").value(value.name()) + ); + } + + @Override + public PacketDumpByteBuf encode(ToIntFunction idGetter, T value) { + return dump("byId", () -> { + dumpValueClass(value); + writer.name("value").value(Objects.toString(value)); + writer.name("id").value(idGetter.applyAsInt(value)); + }); + } + + @Override + public PacketDumpByteBuf writeUuid(UUID uuid) { + return dumpAsString("uuid", uuid); + } + + @Override + public PacketDumpByteBuf writeVarInt(int input) { + return dumpSimple("varInt", input, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeVarLong(long value) { + return dumpSimple("varLong", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeNbt(@Nullable NbtElement NbtElement) { + return dumpAsString("nbt", NbtElement); + } + + @Override + public PacketDumpByteBuf writeItemStack(ItemStack stack) { + return dump("item", () -> writer + .name("item").value(stack.getRegistryEntry().getKey().map(k -> k.getValue().toString()).orElse(null)) + .name("count").value(stack.getCount()) + .name("tag").value(Objects.toString(stack.getNbt())) + ); + } + + @Override + public PacketByteBuf writeString(String string) { + return dump("utf", () -> writer + .name("value").value(string) + ); + } + + @Override + public PacketDumpByteBuf writeString(String string, int maxLength) { + return dump("utf", () -> writer + .name("maxLength").value(maxLength) + .name("value").value(string) + ); + } + + @Override + public PacketDumpByteBuf writeIdentifier(Identifier identifier) { + return dumpAsString("resourceLocation", identifier); + } + + @Override + public void writeRegistryKey(RegistryKey registryKey) { + dump("resourceKey", () -> writer + .name("registry").value(registryKey.getRegistry().toString()) + .name("location").value(registryKey.getValue().toString()) + ); + } + + @Override + public PacketDumpByteBuf writeDate(Date time) { + return dumpSimple("date", ISO_8601.format(time), JsonWriter::value); + } + + @Override + public void writeInstant(Instant instant) { + dumpAsString("instant", instant); + } + + @Override + public PacketDumpByteBuf writePublicKey(PublicKey publicKey) { + return dump("publicKey", () -> writer + .name("encoded").value(Base64.getEncoder().encodeToString(publicKey.getEncoded())) + ); + } + + @Override + public void writeBlockHitResult(BlockHitResult result) { + dump("blockHitResult", () -> writer + .name("pos").beginObject() + .name("x").value(result.getBlockPos().getX()) + .name("y").value(result.getBlockPos().getY()) + .name("z").value(result.getBlockPos().getZ()).endObject() + .name("direction").value(result.getSide().getName()) + .name("offset").beginObject() + .name("x").value(result.getPos().x - result.getBlockPos().getX()) + .name("y").value(result.getPos().y - result.getBlockPos().getY()) + .name("z").value(result.getPos().z - result.getBlockPos().getZ()).endObject() + .name("isInside").value(result.isInsideBlock()) + ); + } + + @Override + public void writeBitSet(BitSet bitSet) { + dump("bitSet", () -> { + writer.name("bits").beginArray(); + IOStream.adapt(bitSet.stream().boxed()).forEach(writer::value); + writer.endArray(); + }); + } + + @Override + public void writeBitSet(BitSet bitSet, int size) { + dump("fixedBitSet", () -> { + writer.name("size").value(size); + writer.name("bits").beginArray(); + IOStream.adapt(bitSet.stream().boxed()).forEach(writer::value); + writer.endArray(); + }); + } + + @Override + public void writeGameProfile(GameProfile gameProfile) { + dump("gameProfile", () -> { + writer.name("value"); + GSON.toJson(gameProfile, GameProfile.class, writer); + }); + } + + @Override + public void writePropertyMap(PropertyMap gameProfileProperties) { + dump("gameProfileProperties", () -> { + writer.name("value"); + GSON.toJson(gameProfileProperties, PropertyMap.class, writer); + }); + } + + @Override + public void writeProperty(Property property) { + dump("property", () -> { + writer.name("name").value(property.name()); + writer.name("value").value(property.value()); + if (property.hasSignature()) { + writer.name("signature").value(property.signature()); + } + }); + } + + @Override + public PacketDumpByteBuf skipBytes(int length) { + return dump("skipBytes", () -> writer.name("length").value(length)); + } + + @Override + public PacketDumpByteBuf writeBoolean(boolean value) { + return dumpSimple("boolean", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeByte(int value) { + return dumpSimple("byte", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeShort(int value) { + return dumpSimple("short", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeShortLE(int value) { + return dumpSimple("shortLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeMedium(int value) { + return dumpSimple("medium", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeMediumLE(int value) { + return dumpSimple("mediumLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeInt(int value) { + return dumpSimple("int", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeIntLE(int value) { + return dumpSimple("intLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeLong(long value) { + return dumpSimple("long", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeLongLE(long value) { + return dumpSimple("longLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeChar(int value) { + return dumpSimple("char", Character.toString((char) value), JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeFloat(float value) { + return dumpSimple("float", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeFloatLE(float value) { + return dumpSimple("floatLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeDouble(double value) { + return dumpSimple("double", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeDoubleLE(double value) { + return dumpSimple("doubleLE", value, JsonWriter::value); + } + + @Override + public PacketDumpByteBuf writeBytes(ByteBuf source) { + return writeBytes(source, source.readableBytes()); + } + + @Override + public PacketDumpByteBuf writeBytes(ByteBuf source, int length) { + byte[] bytes = new byte[length]; + source.readBytes(bytes); + return dumpBytes(bytes); + } + + @Override + public PacketDumpByteBuf writeBytes(ByteBuf source, int sourceIndex, int length) { + byte[] bytes = new byte[length]; + source.getBytes(sourceIndex, bytes); + return dumpBytes(bytes); + } + + @Override + public PacketDumpByteBuf writeBytes(byte[] source) { + return dumpBytes(source); + } + + @Override + public PacketDumpByteBuf writeBytes(byte[] source, int sourceIndex, int length) { + return dumpBytes(Arrays.copyOfRange(source, sourceIndex, sourceIndex + length)); + } + + @Override + public PacketDumpByteBuf writeBytes(ByteBuffer source) { + byte[] bytes = new byte[source.remaining()]; + source.get(bytes); + return dumpBytes(bytes); + } + + @Override + public int writeBytes(InputStream inputStream, int i) throws IOException { + byte[] bytes = new byte[i]; + int read = inputStream.read(bytes); + dumpBytes(Arrays.copyOf(bytes, i)); + return read; + } + + @Override + public int writeBytes(ScatteringByteChannel scatteringByteChannel, int i) throws IOException { + ByteBuffer buffer = ByteBuffer.allocate(i); + int read = scatteringByteChannel.read(buffer); + buffer.flip(); + dumpBytes(Arrays.copyOfRange( + buffer.array(), + buffer.arrayOffset() + buffer.position(), + buffer.arrayOffset() + buffer.limit() + )); + return read; + } + + @Override + public int writeBytes(FileChannel fileChannel, long l, int i) throws IOException { + return writeBytes(fileChannel.position(l), i); + } + + private PacketDumpByteBuf dumpBytes(byte[] bytes) { + return dump("bytes", () -> writer + .name("length").value(bytes.length) + .name("value").value(Base64.getEncoder().encodeToString(bytes)) + ); + } + + @Override + public PacketDumpByteBuf writeZero(int length) { + return dump("zero", () -> writer.name("length").value(length)); + } + + @Override + public int writeCharSequence(CharSequence charSequence, Charset charset) { + String string = charSequence.toString(); + byte[] encoded = string.getBytes(charset); + dump("charSequence", () -> writer + .name("charset").value(charset.name()) + .name("value").value(string) + .name("encoded").value(Base64.getEncoder().encodeToString(encoded)) + ); + return encoded.length; + } + + private void dumpValueClass(Object value) throws IOException { + writer.name("valueClass"); + if (value != null) { + writer.value(MappingsHelper.namedOrIntermediaryToMojmap_class(value.getClass().getName().replace('.', '/')).orElseThrow()); + } else { + writer.nullValue(); + } + } + + private void dumpValue(T value, PacketWriter valueWriter) throws IOException { + writer.beginObject(); + dumpValueClass(value); + writer.name("fields").beginArray(); + valueWriter.accept(this, value); + writer.endArray(); + writer.endObject(); + } + + private PacketDumpByteBuf dumpAsString(String type, Object value) { + return dumpSimple(type, value != null ? value.toString() : null, JsonWriter::value); + } + + private PacketDumpByteBuf dumpSimple(String type, T value, IOBiConsumer valueWriter) { + return dump(type, () -> { + writer.name("value"); + valueWriter.accept(writer, value); + }); + } + + private PacketDumpByteBuf dump(String type, IORunnable dumper) { + Uncheck.run(() -> { + writer.beginObject(); + writer.name("type").value(type); + dumper.run(); + writer.endObject(); + }); + return this; + } + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java b/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java new file mode 100644 index 000000000..a7dfe86f7 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java @@ -0,0 +1,19 @@ +package net.earthcomputer.clientcommands; + +import java.lang.reflect.Field; +import java.util.stream.Stream; + +public class ReflectionUtils { + public static Stream getAllFields(Class clazz) { + Stream.Builder builder = Stream.builder(); + Class targetClass = clazz; + while (targetClass.getSuperclass() != null) { + Field[] fields = targetClass.getDeclaredFields(); + for (Field field : fields) { + builder.add(field); + } + targetClass = targetClass.getSuperclass(); + } + return builder.build(); + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java new file mode 100644 index 000000000..18b3c8d2a --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java @@ -0,0 +1,34 @@ +package net.earthcomputer.clientcommands; + +import sun.misc.Unsafe; + +import java.lang.invoke.MethodHandles; +import java.lang.reflect.Field; + +/** + * @author Gaming32 + */ +public class UnsafeUtils { + private static final Unsafe UNSAFE; + private static final MethodHandles.Lookup IMPL_LOOKUP; + + static { + try { + final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); + unsafeField.setAccessible(true); + UNSAFE = (Unsafe) unsafeField.get(null); + final Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); + IMPL_LOOKUP = (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookupField), UNSAFE.staticFieldOffset(implLookupField)); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static Unsafe getUnsafe() { + return UNSAFE; + } + + public static MethodHandles.Lookup getImplLookup() { + return IMPL_LOOKUP; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java new file mode 100644 index 000000000..3969bb1b1 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -0,0 +1,267 @@ +package net.earthcomputer.clientcommands.command; + +import com.google.gson.stream.JsonWriter; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.Message; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; +import it.unimi.dsi.fastutil.objects.ReferenceSet; +import net.earthcomputer.clientcommands.*; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.packet.Packet; +import net.minecraft.registry.Registry; +import net.minecraft.registry.RegistryKey; +import net.minecraft.registry.entry.RegistryEntry; +import net.minecraft.text.ClickEvent; +import net.minecraft.text.HoverEvent; +import net.minecraft.text.MutableText; +import net.minecraft.text.Text; +import net.minecraft.util.Formatting; +import net.minecraft.util.Identifier; +import net.minecraft.util.math.ChunkPos; + +import java.io.IOException; +import java.io.StringWriter; +import java.lang.invoke.VarHandle; +import java.lang.reflect.Array; +import java.lang.reflect.InaccessibleObjectException; +import java.lang.reflect.Modifier; +import java.time.Instant; +import java.util.*; + +import static net.earthcomputer.clientcommands.command.arguments.MojmapPacketClassArgumentType.*; +import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; + +public class ListenCommand { + + private static final SimpleCommandExceptionType ALREADY_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.clisten.add.failed")); + private static final SimpleCommandExceptionType NOT_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Text.translatable("commands.clisten.remove.failed")); + + private static final Set>> packets = new HashSet<>(); + + private static PacketCallback callback; + + private static final ThreadLocal> SEEN = ThreadLocal.withInitial(ReferenceOpenHashSet::new); + + public static void register(CommandDispatcher dispatcher) { + dispatcher.register(literal("clisten") + .then(literal("add") + .then(argument("packet", packet()) + .executes(ctx -> add(ctx.getSource(), getPacket(ctx, "packet"))))) + .then(literal("remove") + .then(argument("packet", packet()) + .executes(ctx -> remove(ctx.getSource(), getPacket(ctx, "packet"))))) + .then(literal("list") + .executes(ctx -> list(ctx.getSource()))) + .then(literal("clear") + .executes(ctx -> clear(ctx.getSource())))); + } + + private static int add(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + if (!packets.add(packetClass)) { + throw ALREADY_LISTENING_EXCEPTION.create(); + } + + source.sendFeedback(Text.translatable("commands.clisten.add.success")); + + if (callback == null) { + callback = (packet, side) -> { + String packetClassName = packet.getClass().getName().replace('.', '/'); + Optional mojmapPacketName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName); + + String packetData; + Text packetDataPreview; + if (Configs.packetDumpMethod == Configs.PacketDumpMethod.BYTE_BUF) { + StringWriter writer = new StringWriter(); + try { + PacketDumper.dumpPacket(packet, new JsonWriter(writer)); + } catch (IOException e) { + e.printStackTrace(); + return; + } + packetData = writer.toString(); + packetDataPreview = Text.literal(packetData.replace("\u00a7", "\\u00a7")); + } else { + try { + packetDataPreview = serialize(packet); + packetData = packetDataPreview.getString(); + } catch (StackOverflowError e) { + e.printStackTrace(); + return; + } + } + + MutableText packetText = Text.literal(mojmapPacketName.orElseThrow().substring(MOJMAP_PACKET_PREFIX.length())).styled(s -> s + .withUnderline(true) + .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, packetDataPreview)) + .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, packetData))); + + switch (side) { + case CLIENTBOUND -> source.sendFeedback(Text.translatable("commands.clisten.receivedPacket", packetText)); + case SERVERBOUND -> source.sendFeedback(Text.translatable("commands.clisten.sentPacket", packetText)); + } + }; + } + + return Command.SINGLE_SUCCESS; + } + + private static int remove(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + if (!packets.remove(packetClass)) { + throw NOT_LISTENING_EXCEPTION.create(); + } + + source.sendFeedback(Text.translatable("commands.clisten.remove.success")); + return Command.SINGLE_SUCCESS; + } + + private static int list(FabricClientCommandSource source) { + int amount = packets.size(); + if (amount == 0) { + source.sendFeedback(Text.translatable("commands.clisten.list.none")); + } else { + source.sendFeedback(Text.translatable("commands.clisten.list")); + packets.forEach(packetClass -> { + String packetClassName = packetClass.getName().replace('.', '/'); + Optional mojmapName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName); + source.sendFeedback(Text.literal(mojmapName.orElseThrow().substring(MOJMAP_PACKET_PREFIX.length()))); + }); + } + + return amount; + } + + private static int clear(FabricClientCommandSource source) { + int amount = packets.size(); + packets.clear(); + source.sendFeedback(Text.translatable("commands.clisten.clear")); + return amount; + } + + private static Text serialize(Object object) { + try { + if (SEEN.get().add(object)) { + return serializeInner(object); + } + return Text.empty(); + } finally { + SEEN.get().remove(object); + if (SEEN.get().isEmpty()) { + SEEN.remove(); + } + } + } + + private static Text serializeInner(Object object) { + if (object == null) { + return Text.literal("null"); + } + if (object instanceof Text text) { + return text; + } + if (object instanceof String string) { + return Text.literal(string); + } + if (object instanceof Number || object instanceof Boolean) { + return Text.literal(object.toString()); + } + if (object instanceof Optional optional) { + return optional.isPresent() ? serialize(optional.get()) : Text.literal("empty"); + } + if (object instanceof Date date) { + return Text.of(date); + } + if (object instanceof Instant instant) { + return Text.of(Date.from(instant)); + } + if (object instanceof UUID uuid) { + return Text.of(uuid); + } + if (object instanceof ChunkPos chunkPos) { + return Text.of(chunkPos); + } + if (object instanceof Identifier identifier) { + return Text.of(identifier); + } + if (object instanceof Message message) { + return Text.of(message); + } + if (object.getClass().isArray()) { + MutableText text = Text.literal("["); + int lengthMinusOne = Array.getLength(object) - 1; + if (lengthMinusOne < 0) { + return text.append("]"); + } + for (int i = 0; i < lengthMinusOne; i++) { + text.append(serialize(Array.get(object, i))).append(", "); + } + return text.append(serialize(Array.get(object, lengthMinusOne))).append("]"); + } + if (object instanceof Collection collection) { + MutableText text = Text.literal("["); + text.append(collection.stream().map(e -> serialize(e).copy()).reduce((l, r) -> l.append(", ").append(r)).orElse(Text.empty())); + return text.append("]"); + } + if (object instanceof Map map) { + MutableText text = Text.literal("{"); + text.append(map.entrySet().stream().map(e -> serialize(e.getKey()).copy().append("=").append(serialize(e.getValue()))).reduce((l, r) -> l.append(", ").append(r)).orElse(Text.empty())); + return text.append("}"); + } + if (object instanceof Registry registry) { + return Text.of(registry.getKey().getValue()); + } + if (object instanceof RegistryKey registryKey) { + MutableText text = Text.literal("{"); + text.append("registry=").append(serialize(registryKey.getRegistry())).append(", "); + text.append("value=").append(serialize(registryKey.getValue())); + return text.append("}"); + } + if (object instanceof RegistryEntry registryEntry) { + MutableText text = Text.literal("{"); + text.append("key=").append(serialize(registryEntry.getKey())).append(", "); + text.append("value=").append(serialize(registryEntry.value())); + return text.append("}"); + } + + String className = object.getClass().getName().replace(".", "/"); + String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); + + MutableText text = Text.literal(mojmapClassName + '{'); + text.append(ReflectionUtils.getAllFields(object.getClass()) + .filter(field -> !Modifier.isStatic(field.getModifiers())) + .map(field -> { + String fieldName = field.getName(); + Optional mojmapFieldName = MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName); + try { + field.setAccessible(true); + return Text.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(field.get(object))); + } catch (InaccessibleObjectException | ReflectiveOperationException e) { + try { + VarHandle varHandle = UnsafeUtils.getImplLookup().findVarHandle(object.getClass(), fieldName, field.getType()); + return Text.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(varHandle.get(object))); + } catch (ReflectiveOperationException ex) { + return Text.literal(mojmapFieldName.orElse(fieldName) + '=').append(Text.translatable("commands.clisten.packetError").formatted(Formatting.DARK_RED)); + } + } + }) + .reduce((l, r) -> l.append(", ").append(r)) + .orElse(Text.empty())); + return text.append("}"); + } + + public static void onPacket(Packet packet, NetworkSide side) { + if (!packets.contains(packet.getClass())) { + return; + } + callback.apply(packet, side); + } + + @FunctionalInterface + private interface PacketCallback { + void apply(Packet packet, NetworkSide side); + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java new file mode 100644 index 000000000..46fea57c7 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -0,0 +1,68 @@ +package net.earthcomputer.clientcommands.command.arguments; + +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import net.earthcomputer.clientcommands.MappingsHelper; +import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.fabricmc.mappingio.tree.MappingTreeView; +import net.minecraft.command.CommandSource; +import net.minecraft.network.packet.Packet; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.stream.Collectors; + +public class MojmapPacketClassArgumentType implements ArgumentType>> { + + private static final Collection EXAMPLES = Arrays.asList("ClientboundPlayerChatPacket", "ClientboundSystemChatMessage", "ServerboundContainerSlotStateChangedPacket"); + + public static final String MOJMAP_PACKET_PREFIX = "net/minecraft/network/protocol/game/"; + + private static final Set mojmapPackets = MappingsHelper.mojmapClasses().stream() + .map(MappingTreeView.ElementMappingView::getSrcName) + .filter(name -> name.startsWith(MOJMAP_PACKET_PREFIX) && name.endsWith("Packet")) + .map(name -> name.substring(MOJMAP_PACKET_PREFIX.length())) + .collect(Collectors.toSet()); + + public static MojmapPacketClassArgumentType packet() { + return new MojmapPacketClassArgumentType(); + } + + @SuppressWarnings("unchecked") + public static Class> getPacket(final CommandContext context, final String name) { + return (Class>) context.getArgument(name, Class.class); + } + + @Override + @SuppressWarnings("unchecked") + public Class> parse(StringReader reader) throws CommandSyntaxException { + String packet = MOJMAP_PACKET_PREFIX + reader.readString(); + Optional mojmapPacketName = MappingsHelper.mojmapToNamedOrIntermediary_class(packet); + if (mojmapPacketName.isEmpty()) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + String packetClass = mojmapPacketName.get().replace('/', '.'); + try { + return (Class>) Class.forName(packetClass); + } catch (ReflectiveOperationException e) { + throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + } + } + + @Override + public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { + return CommandSource.suggestMatching(mojmapPackets, builder); + } + + @Override + public Collection getExamples() { + return EXAMPLES; + } +} diff --git a/src/main/java/net/earthcomputer/clientcommands/mixin/MixinClientConnection.java b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinClientConnection.java new file mode 100644 index 000000000..8cd3a04f0 --- /dev/null +++ b/src/main/java/net/earthcomputer/clientcommands/mixin/MixinClientConnection.java @@ -0,0 +1,34 @@ +package net.earthcomputer.clientcommands.mixin; + +import io.netty.channel.ChannelHandlerContext; +import net.earthcomputer.clientcommands.command.ListenCommand; +import net.minecraft.network.ClientConnection; +import net.minecraft.network.NetworkSide; +import net.minecraft.network.PacketCallbacks; +import net.minecraft.network.packet.Packet; +import org.jetbrains.annotations.Nullable; +import org.spongepowered.asm.mixin.Final; +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(ClientConnection.class) +public class MixinClientConnection { + @Shadow @Final private NetworkSide side; + + @Inject(method = "channelRead0(Lio/netty/channel/ChannelHandlerContext;Lnet/minecraft/network/packet/Packet;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/network/ClientConnection;handlePacket(Lnet/minecraft/network/packet/Packet;Lnet/minecraft/network/listener/PacketListener;)V")) + private void onPacketReceive(ChannelHandlerContext channelHandlerContext, Packet packet, CallbackInfo ci) { + if (this.side == NetworkSide.CLIENTBOUND) { + ListenCommand.onPacket(packet, NetworkSide.CLIENTBOUND); + } + } + + @Inject(method = "sendInternal", at = @At("HEAD")) + private void onPacketSend(Packet packet, @Nullable PacketCallbacks callbacks, boolean flush, CallbackInfo ci) { + if (this.side == NetworkSide.CLIENTBOUND) { + ListenCommand.onPacket(packet, NetworkSide.SERVERBOUND); + } + } +} diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 2366a582c..cca7244fe 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -137,6 +137,17 @@ "commands.ckit.list": "Available kits: %s", "commands.ckit.list.empty": "No available kits", + "commands.clisten.receivedPacket": "Received the following packet: %s", + "commands.clisten.sentPacket": "Sent the following packet: %s", + "commands.clisten.packetError": "ERROR", + "commands.clisten.add.success": "Successfully started listening to that packet", + "commands.clisten.add.failed": "Already listening to that packet", + "commands.clisten.remove.success": "No longer listening to that packet", + "commands.clisten.remove.failed": "Not listening to that packet", + "commands.clisten.list.none": "Not listening to any packets", + "commands.clisten.list": "Listening to the following packets:", + "commands.clisten.clear": "No longer listening to any packets", + "commands.cplayerinfo.ioException": "An error occurred", "commands.cplayerinfo.getNameHistory.success": "%s has had the following names: %s", diff --git a/src/main/resources/mixins.clientcommands.json b/src/main/resources/mixins.clientcommands.json index 28be2c28a..1d62a7ae1 100644 --- a/src/main/resources/mixins.clientcommands.json +++ b/src/main/resources/mixins.clientcommands.json @@ -17,6 +17,7 @@ "MixinChatHud", "MixinChatScreen", "MixinClientCommandSource", + "MixinClientConnection", "MixinClientPlayerEntity", "MixinClientPlayerInteractionManager", "MixinClientPlayNetworkHandler", From 42af6e6ea7adec08274c5b9b46cc490a22b2bacb Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Mon, 12 Feb 2024 13:51:18 +0100 Subject: [PATCH 02/15] Move classes to features package --- .../java/net/earthcomputer/clientcommands/ClientCommands.java | 2 +- .../net/earthcomputer/clientcommands/command/ListenCommand.java | 2 ++ .../command/arguments/MojmapPacketClassArgumentType.java | 2 +- .../clientcommands/{ => features}/MappingsHelper.java | 2 +- .../clientcommands/{ => features}/PacketDumper.java | 2 +- 5 files changed, 6 insertions(+), 4 deletions(-) rename src/main/java/net/earthcomputer/clientcommands/{ => features}/MappingsHelper.java (99%) rename src/main/java/net/earthcomputer/clientcommands/{ => features}/PacketDumper.java (99%) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index 92b73c70d..577f7c4f7 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -7,6 +7,7 @@ import dev.xpple.betterconfig.api.ModConfigBuilder; import io.netty.buffer.Unpooled; import net.earthcomputer.clientcommands.command.*; +import net.earthcomputer.clientcommands.features.MappingsHelper; import net.earthcomputer.clientcommands.render.RenderQueue; import net.fabricmc.api.ClientModInitializer; import net.fabricmc.fabric.api.client.command.v2.ClientCommandRegistrationCallback; @@ -15,7 +16,6 @@ import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.loader.api.FabricLoader; import net.minecraft.DetectedVersion; -import net.minecraft.SharedConstants; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.commands.CommandBuildContext; diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index d628a7bc8..9a0de9dc6 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -9,6 +9,8 @@ import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import it.unimi.dsi.fastutil.objects.ReferenceSet; import net.earthcomputer.clientcommands.*; +import net.earthcomputer.clientcommands.features.MappingsHelper; +import net.earthcomputer.clientcommands.features.PacketDumper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.ChatFormatting; import net.minecraft.core.Holder; diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index 0ca029e43..d86765124 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -6,7 +6,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import net.earthcomputer.clientcommands.MappingsHelper; +import net.earthcomputer.clientcommands.features.MappingsHelper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.fabricmc.mappingio.tree.MappingTreeView; import net.minecraft.commands.SharedSuggestionProvider; diff --git a/src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java similarity index 99% rename from src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java rename to src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 7dcabd89d..f0dec1ad4 100644 --- a/src/main/java/net/earthcomputer/clientcommands/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -1,4 +1,4 @@ -package net.earthcomputer.clientcommands; +package net.earthcomputer.clientcommands.features; import com.google.gson.JsonElement; import com.google.gson.JsonObject; diff --git a/src/main/java/net/earthcomputer/clientcommands/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java similarity index 99% rename from src/main/java/net/earthcomputer/clientcommands/PacketDumper.java rename to src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java index 60650a38c..854d58f7a 100644 --- a/src/main/java/net/earthcomputer/clientcommands/PacketDumper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java @@ -1,4 +1,4 @@ -package net.earthcomputer.clientcommands; +package net.earthcomputer.clientcommands.features; import com.google.gson.Gson; import com.google.gson.GsonBuilder; From 00172ab6fdde816372d008e2dcd85fffbebdd9f2 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Sun, 18 Feb 2024 22:29:57 +0100 Subject: [PATCH 03/15] Implement most requested changes --- .../clientcommands/ClientCommands.java | 4 +- .../earthcomputer/clientcommands/Configs.java | 3 + .../clientcommands/ReflectionUtils.java | 6 +- .../clientcommands/UnsafeUtils.java | 36 +++-- .../clientcommands/command/ListenCommand.java | 79 ++++++----- .../MojmapPacketClassArgumentType.java | 51 +++---- .../features/MappingsHelper.java | 126 +++++++++++------- .../clientcommands/features/PacketDumper.java | 20 ++- .../assets/clientcommands/lang/en_us.json | 1 + src/main/resources/clientcommands.aw | 4 + 10 files changed, 212 insertions(+), 118 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index 577f7c4f7..061b3f1ad 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -164,7 +164,9 @@ public static void registerCommands(CommandDispatcher CrackRNGCommand.register(dispatcher); WeatherCommand.register(dispatcher); PluginsCommand.register(dispatcher); - ListenCommand.register(dispatcher); + if (ListenCommand.isEnabled) { + ListenCommand.register(dispatcher); + } Calendar calendar = Calendar.getInstance(); boolean registerChatCommand = calendar.get(Calendar.MONTH) == Calendar.APRIL && calendar.get(Calendar.DAY_OF_MONTH) == 1; diff --git a/src/main/java/net/earthcomputer/clientcommands/Configs.java b/src/main/java/net/earthcomputer/clientcommands/Configs.java index f8e6dfe6f..9c2822f43 100644 --- a/src/main/java/net/earthcomputer/clientcommands/Configs.java +++ b/src/main/java/net/earthcomputer/clientcommands/Configs.java @@ -121,4 +121,7 @@ public enum PacketDumpMethod { REFLECTION, BYTE_BUF, } + + @Config + public static int maximumPacketFieldDepth = 10; } diff --git a/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java b/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java index a7dfe86f7..512453983 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java +++ b/src/main/java/net/earthcomputer/clientcommands/ReflectionUtils.java @@ -3,7 +3,11 @@ import java.lang.reflect.Field; import java.util.stream.Stream; -public class ReflectionUtils { +public final class ReflectionUtils { + + private ReflectionUtils() { + } + public static Stream getAllFields(Class clazz) { Stream.Builder builder = Stream.builder(); Class targetClass = clazz; diff --git a/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java index 18b3c8d2a..3090adb7b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java +++ b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java @@ -1,5 +1,9 @@ package net.earthcomputer.clientcommands; +import com.mojang.logging.LogUtils; +import net.earthcomputer.clientcommands.command.ListenCommand; +import net.minecraft.Util; +import org.slf4j.Logger; import sun.misc.Unsafe; import java.lang.invoke.MethodHandles; @@ -8,21 +12,37 @@ /** * @author Gaming32 */ -public class UnsafeUtils { - private static final Unsafe UNSAFE; - private static final MethodHandles.Lookup IMPL_LOOKUP; +public final class UnsafeUtils { - static { + private UnsafeUtils() { + } + + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final Unsafe UNSAFE = Util.make(() -> { try { final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); - UNSAFE = (Unsafe) unsafeField.get(null); + return (Unsafe) unsafeField.get(null); + } catch (Exception e) { + LOGGER.error("Could not access theUnsafe", e); + return null; + } + }); + + private static final MethodHandles.Lookup IMPL_LOOKUP = Util.make(() -> { + try { + //noinspection ConstantValue + if (UNSAFE == null) { + return null; + } final Field implLookupField = MethodHandles.Lookup.class.getDeclaredField("IMPL_LOOKUP"); - IMPL_LOOKUP = (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookupField), UNSAFE.staticFieldOffset(implLookupField)); + return (MethodHandles.Lookup) UNSAFE.getObject(UNSAFE.staticFieldBase(implLookupField), UNSAFE.staticFieldOffset(implLookupField)); } catch (Exception e) { - throw new RuntimeException(e); + LOGGER.error("Could not access IMPL_LOOKUP", e); + return null; } - } + }); public static Unsafe getUnsafe() { return UNSAFE; diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index 9a0de9dc6..edf69112e 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -6,9 +6,11 @@ import com.mojang.brigadier.Message; import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.logging.LogUtils; import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; -import it.unimi.dsi.fastutil.objects.ReferenceSet; -import net.earthcomputer.clientcommands.*; +import net.earthcomputer.clientcommands.Configs; +import net.earthcomputer.clientcommands.ReflectionUtils; +import net.earthcomputer.clientcommands.UnsafeUtils; import net.earthcomputer.clientcommands.features.MappingsHelper; import net.earthcomputer.clientcommands.features.PacketDumper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; @@ -24,29 +26,39 @@ import net.minecraft.resources.ResourceKey; import net.minecraft.resources.ResourceLocation; import net.minecraft.world.level.ChunkPos; +import org.slf4j.Logger; import java.io.IOException; import java.io.StringWriter; +import java.lang.invoke.MethodHandles; import java.lang.invoke.VarHandle; import java.lang.reflect.Array; import java.lang.reflect.InaccessibleObjectException; import java.lang.reflect.Modifier; import java.time.Instant; -import java.util.*; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import static net.earthcomputer.clientcommands.command.arguments.MojmapPacketClassArgumentType.*; import static net.fabricmc.fabric.api.client.command.v2.ClientCommandManager.*; public class ListenCommand { + public static boolean isEnabled = true; + private static final SimpleCommandExceptionType ALREADY_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.clisten.add.failed")); private static final SimpleCommandExceptionType NOT_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.clisten.remove.failed")); - private static final Set>> packets = new HashSet<>(); + private static final Logger LOGGER = LogUtils.getLogger(); - private static PacketCallback callback; + private static final Set>> packets = new HashSet<>(); - private static final ThreadLocal> SEEN = ThreadLocal.withInitial(ReferenceOpenHashSet::new); + private static PacketCallback callback; public static void register(CommandDispatcher dispatcher) { dispatcher.register(literal("clisten") @@ -62,7 +74,7 @@ public static void register(CommandDispatcher dispatc .executes(ctx -> clear(ctx.getSource())))); } - private static int add(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + private static int add(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { if (!packets.add(packetClass)) { throw ALREADY_LISTENING_EXCEPTION.create(); } @@ -81,22 +93,22 @@ private static int add(FabricClientCommandSource source, Class> packet try { PacketDumper.dumpPacket(packet, new JsonWriter(writer)); } catch (IOException e) { - e.printStackTrace(); + LOGGER.error("Could not dump packet", e); return; } packetData = writer.toString(); packetDataPreview = Component.literal(packetData.replace("\u00a7", "\\u00a7")); } else { try { - packetDataPreview = serialize(packet); + packetDataPreview = serialize(packet, new ReferenceOpenHashSet<>(), 0); packetData = packetDataPreview.getString(); } catch (StackOverflowError e) { - e.printStackTrace(); + LOGGER.error("Could not serialize packet into a Component", e); return; } } - MutableComponent packetComponent = Component.literal(mojmapPacketName.orElseThrow().substring(MOJMAP_PACKET_PREFIX.length())).withStyle(s -> s + MutableComponent packetComponent = Component.literal(mojmapPacketName.map(packetName -> packetName.substring(packetName.lastIndexOf('/') + 1)).orElseThrow()).withStyle(s -> s .withUnderlined(true) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, packetDataPreview)) .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, packetData))); @@ -111,7 +123,7 @@ private static int add(FabricClientCommandSource source, Class> packet return Command.SINGLE_SUCCESS; } - private static int remove(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + private static int remove(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { if (!packets.remove(packetClass)) { throw NOT_LISTENING_EXCEPTION.create(); } @@ -129,7 +141,7 @@ private static int list(FabricClientCommandSource source) { packets.forEach(packetClass -> { String packetClassName = packetClass.getName().replace('.', '/'); Optional mojmapName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName); - source.sendFeedback(Component.literal(mojmapName.orElseThrow().substring(MOJMAP_PACKET_PREFIX.length()))); + source.sendFeedback(Component.literal(mojmapName.map(name -> name.substring(name.lastIndexOf('/' + 1))).orElseThrow())); }); } @@ -143,21 +155,18 @@ private static int clear(FabricClientCommandSource source) { return amount; } - private static Component serialize(Object object) { + private static Component serialize(Object object, Set seen, int depth) { try { - if (SEEN.get().add(object)) { - return serializeInner(object); + if (depth <= Configs.maximumPacketFieldDepth && seen.add(object)) { + return serializeInner(object, seen, depth); } return Component.empty(); } finally { - SEEN.get().remove(object); - if (SEEN.get().isEmpty()) { - SEEN.remove(); - } + seen.remove(object); } } - private static Component serializeInner(Object object) { + private static Component serializeInner(Object object, Set seen, int depth) { if (object == null) { return Component.literal("null"); } @@ -171,7 +180,7 @@ private static Component serializeInner(Object object) { return Component.literal(object.toString()); } if (object instanceof Optional optional) { - return optional.isPresent() ? serialize(optional.get()) : Component.literal("empty"); + return optional.isPresent() ? serialize(optional.get(), seen, depth + 1) : Component.literal("empty"); } if (object instanceof Date date) { return Component.translationArg(date); @@ -198,18 +207,18 @@ private static Component serializeInner(Object object) { return component.append("]"); } for (int i = 0; i < lengthMinusOne; i++) { - component.append(serialize(Array.get(object, i))).append(", "); + component.append(serialize(Array.get(object, i), seen, depth + 1)).append(", "); } - return component.append(serialize(Array.get(object, lengthMinusOne))).append("]"); + return component.append(serialize(Array.get(object, lengthMinusOne), seen, depth + 1)).append("]"); } if (object instanceof Collection collection) { MutableComponent component = Component.literal("["); - component.append(collection.stream().map(e -> serialize(e).copy()).reduce((l, r) -> l.append(", ").append(r)).orElse(Component.empty())); + component.append(collection.stream().map(e -> serialize(e, seen, depth + 1).copy()).reduce((l, r) -> l.append(", ").append(r)).orElse(Component.empty())); return component.append("]"); } if (object instanceof Map map) { MutableComponent component = Component.literal("{"); - component.append(map.entrySet().stream().map(e -> serialize(e.getKey()).copy().append("=").append(serialize(e.getValue()))).reduce((l, r) -> l.append(", ").append(r)).orElse(Component.empty())); + component.append(map.entrySet().stream().map(e -> serialize(e.getKey(), seen, depth + 1).copy().append("=").append(serialize(e.getValue(), seen, depth + 1))).reduce((l, r) -> l.append(", ").append(r)).orElse(Component.empty())); return component.append("}"); } if (object instanceof Registry registry) { @@ -217,14 +226,14 @@ private static Component serializeInner(Object object) { } if (object instanceof ResourceKey resourceKey) { MutableComponent component = Component.literal("{"); - component.append("registry=").append(serialize(resourceKey.registry())).append(", "); - component.append("location=").append(serialize(resourceKey.location())); + component.append("registry=").append(serialize(resourceKey.registry(), seen, depth + 1)).append(", "); + component.append("location=").append(serialize(resourceKey.location(), seen, depth + 1)); return component.append("}"); } if (object instanceof Holder holder) { MutableComponent component = Component.literal("{"); - component.append("kind=").append(serialize(holder.kind().name())).append(", "); - component.append("value=").append(serialize(holder.value())); + component.append("kind=").append(serialize(holder.kind().name(), seen, depth + 1)).append(", "); + component.append("value=").append(serialize(holder.value(), seen, depth + 1)); return component.append("}"); } @@ -240,11 +249,15 @@ private static Component serializeInner(Object object) { Optional mojmapFieldName = MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName); try { field.setAccessible(true); - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(field.get(object))); + return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(field.get(object), seen, depth + 1)); } catch (InaccessibleObjectException | ReflectiveOperationException e) { try { - VarHandle varHandle = UnsafeUtils.getImplLookup().findVarHandle(object.getClass(), fieldName, field.getType()); - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(varHandle.get(object))); + MethodHandles.Lookup implLookup = UnsafeUtils.getImplLookup(); + if (implLookup == null) { + return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); + } + VarHandle varHandle = implLookup.findVarHandle(object.getClass(), fieldName, field.getType()); + return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(varHandle.get(object), seen, depth + 1)); } catch (ReflectiveOperationException ex) { return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index d86765124..2e9f06c14 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -4,61 +4,66 @@ import com.mojang.brigadier.arguments.ArgumentType; import com.mojang.brigadier.context.CommandContext; import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.DynamicCommandExceptionType; import com.mojang.brigadier.suggestion.Suggestions; import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.earthcomputer.clientcommands.features.MappingsHelper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; -import net.fabricmc.mappingio.tree.MappingTreeView; import net.minecraft.commands.SharedSuggestionProvider; +import net.minecraft.network.ConnectionProtocol; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; +import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.Collection; +import java.util.Map; +import java.util.Objects; import java.util.Optional; -import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; -public class MojmapPacketClassArgumentType implements ArgumentType>> { +public class MojmapPacketClassArgumentType implements ArgumentType>> { private static final Collection EXAMPLES = Arrays.asList("ClientboundPlayerChatPacket", "ClientboundSystemChatMessage", "ServerboundContainerSlotStateChangedPacket"); - public static final String MOJMAP_PACKET_PREFIX = "net/minecraft/network/protocol/game/"; + private static final DynamicCommandExceptionType UNKNOWN_PACKET_EXCEPTION = new DynamicCommandExceptionType(packet -> Component.translatable("commands.clisten.unknownPacket", packet)); - private static final Set mojmapPackets = MappingsHelper.mojmapClasses().stream() - .map(MappingTreeView.ElementMappingView::getSrcName) - .filter(name -> name.startsWith(MOJMAP_PACKET_PREFIX) && name.endsWith("Packet")) - .map(name -> name.substring(MOJMAP_PACKET_PREFIX.length())) - .collect(Collectors.toSet()); + private static final Map>> mojmapPackets = Arrays.stream(ConnectionProtocol.values()) + .flatMap(connectionProtocol -> connectionProtocol.flows.values().stream() + .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream())) + .map(clazz -> { + Optional mojmapPacket = MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')); + return mojmapPacket.map(packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz)).orElse(null); + }) + .collect(Collectors.filtering(Objects::nonNull, Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue, (l, r) -> l))); public static MojmapPacketClassArgumentType packet() { return new MojmapPacketClassArgumentType(); } @SuppressWarnings("unchecked") - public static Class> getPacket(final CommandContext context, final String name) { - return (Class>) context.getArgument(name, Class.class); + public static Class> getPacket(final CommandContext context, final String name) { + return (Class>) context.getArgument(name, Class.class); } @Override - @SuppressWarnings("unchecked") - public Class> parse(StringReader reader) throws CommandSyntaxException { - String packet = MOJMAP_PACKET_PREFIX + reader.readString(); - Optional mojmapPacketName = MappingsHelper.mojmapToNamedOrIntermediary_class(packet); - if (mojmapPacketName.isEmpty()) { - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + public Class> parse(StringReader reader) throws CommandSyntaxException { + final int start = reader.getCursor(); + while (reader.canRead() && (StringReader.isAllowedInUnquotedString(reader.peek()) || reader.peek() == '$' )) { + reader.skip(); } - String packetClass = mojmapPacketName.get().replace('/', '.'); - try { - return (Class>) Class.forName(packetClass); - } catch (ReflectiveOperationException e) { - throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create(); + String packet = reader.getString().substring(start, reader.getCursor()); + Class> packetClass = mojmapPackets.get(packet); + if (packetClass == null) { + throw UNKNOWN_PACKET_EXCEPTION.create(packet); } + return packetClass; } @Override public CompletableFuture listSuggestions(CommandContext context, SuggestionsBuilder builder) { - return SharedSuggestionProvider.suggest(mojmapPackets, builder); + return SharedSuggestionProvider.suggest(mojmapPackets.keySet(), builder); } @Override diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index f0dec1ad4..86ec68401 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -3,12 +3,18 @@ import com.google.gson.JsonElement; import com.google.gson.JsonObject; import com.google.gson.JsonParser; +import com.mojang.logging.LogUtils; +import net.earthcomputer.clientcommands.ClientCommands; +import net.earthcomputer.clientcommands.command.ListenCommand; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.mappingio.MappingReader; import net.fabricmc.mappingio.format.MappingFormat; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; +import org.slf4j.Logger; +import java.io.BufferedReader; +import java.io.BufferedWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -17,12 +23,19 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; import java.time.Duration; import java.util.Collection; import java.util.Optional; public class MappingsHelper { + private static final Logger LOGGER = LogUtils.getLogger(); + + private static final Path MAPPINGS_DIR = ClientCommands.configDir.resolve("mappings"); + private static final MemoryMappingTree mojmapOfficial = new MemoryMappingTree(); private static final int SRC_OFFICIAL = 0; private static final int DEST_OFFICIAL = 0; @@ -35,55 +48,69 @@ public class MappingsHelper { private static final boolean IS_DEV_ENV = FabricLoader.getInstance().isDevelopmentEnvironment(); - private static final HttpClient httpClient = HttpClient.newHttpClient(); - public static void initMappings(String version) { - HttpRequest versionsRequest = HttpRequest.newBuilder() - .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")) - .GET() - .timeout(Duration.ofSeconds(5)) - .build(); - httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenAccept(versionsBody -> { - JsonObject versionsJson = JsonParser.parseString(versionsBody).getAsJsonObject(); - String versionUrl = versionsJson.getAsJsonArray("versions").asList().stream() - .map(JsonElement::getAsJsonObject) - .filter(v -> v.get("id").getAsString().equals(version)) - .map(v -> v.get("url").getAsString()) - .findAny().orElseThrow(); - - HttpRequest versionRequest = HttpRequest.newBuilder() - .uri(URI.create(versionUrl)) - .GET() - .timeout(Duration.ofSeconds(5)) - .build(); - httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenAccept(versionBody -> { - JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); - String mappingsUrl = versionJson - .getAsJsonObject("downloads") - .getAsJsonObject("client_mappings") - .get("url").getAsString(); - - HttpRequest mappingsRequest = HttpRequest.newBuilder() - .uri(URI.create(mappingsUrl)) - .GET() - .timeout(Duration.ofSeconds(5)) - .build(); - httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenApply(StringReader::new) - .thenAccept(reader -> { - try { - MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - }); - }); + try { + Files.createDirectories(MAPPINGS_DIR); + } catch (IOException e) { + LOGGER.error("Failed to create mappings dir", e); + } + try (BufferedReader reader = Files.newBufferedReader(MAPPINGS_DIR.resolve(version + ".txt"))) { + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + } catch (IOException e) { + mojmapOfficial.reset(); + HttpClient httpClient = HttpClient.newHttpClient(); + HttpRequest versionsRequest = HttpRequest.newBuilder() + .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(versionsBody -> { + JsonObject versionsJson = JsonParser.parseString(versionsBody).getAsJsonObject(); + String versionUrl = versionsJson.getAsJsonArray("versions").asList().stream() + .map(JsonElement::getAsJsonObject) + .filter(v -> v.get("id").getAsString().equals(version)) + .map(v -> v.get("url").getAsString()) + .findAny().orElseThrow(); + + HttpRequest versionRequest = HttpRequest.newBuilder() + .uri(URI.create(versionUrl)) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(versionBody -> { + JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); + String mappingsUrl = versionJson + .getAsJsonObject("downloads") + .getAsJsonObject("client_mappings") + .get("url").getAsString(); + + HttpRequest mappingsRequest = HttpRequest.newBuilder() + .uri(URI.create(mappingsUrl)) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()) + .thenApply(HttpResponse::body) + .thenAccept(body -> { + try (StringReader reader = new StringReader(body)) { + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + } catch (IOException ex) { + LOGGER.error("Could not read ProGuard mappings file", ex); + ListenCommand.isEnabled = false; + } + try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { + writer.write(body); + } catch (IOException ex) { + LOGGER.error("Could not write ProGuard mappings file", ex); + } + }); + }); + }); + } try (InputStream stream = FabricLoader.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny")) { if (stream == null) { @@ -91,7 +118,8 @@ public static void initMappings(String version) { } MappingReader.read(new InputStreamReader(stream), IS_DEV_ENV ? MappingFormat.TINY_2_FILE : MappingFormat.TINY_FILE, officialIntermediaryNamed); } catch (IOException e) { - throw new RuntimeException(e); + LOGGER.error("Could not read mappings.tiny", e); + ListenCommand.isEnabled = false; } } diff --git a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java index 854d58f7a..ba8ebf33a 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java @@ -19,7 +19,12 @@ import io.netty.handler.codec.EncoderException; import it.unimi.dsi.fastutil.ints.IntList; import net.minecraft.Util; -import net.minecraft.core.*; +import net.minecraft.core.BlockPos; +import net.minecraft.core.GlobalPos; +import net.minecraft.core.Holder; +import net.minecraft.core.IdMap; +import net.minecraft.core.Registry; +import net.minecraft.core.SectionPos; import net.minecraft.nbt.Tag; import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.chat.Component; @@ -50,7 +55,16 @@ import java.text.DateFormat; import java.text.SimpleDateFormat; import java.time.Instant; -import java.util.*; +import java.util.Arrays; +import java.util.Base64; +import java.util.BitSet; +import java.util.Collection; +import java.util.Date; +import java.util.EnumSet; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.UUID; import java.util.function.ToIntFunction; /** @@ -67,7 +81,7 @@ public static void dumpPacket(Packet packet, JsonWriter writer) throws IOExce writer.endArray(); } - public static class PacketDumpByteBuf extends FriendlyByteBuf { + private static class PacketDumpByteBuf extends FriendlyByteBuf { private static final Gson GSON = new GsonBuilder() .registerTypeAdapter(UUID.class, new UUIDTypeAdapter()) .registerTypeAdapter(Instant.class, new InstantTypeAdapter()) diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 6b55459f0..ae0839f6f 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -137,6 +137,7 @@ "commands.ckit.list": "Available kits: %s", "commands.ckit.list.empty": "No available kits", + "commands.clisten.unknownPacket": "Unknown packet %s", "commands.clisten.receivedPacket": "Received the following packet: %s", "commands.clisten.sentPacket": "Sent the following packet: %s", "commands.clisten.packetError": "ERROR", diff --git a/src/main/resources/clientcommands.aw b/src/main/resources/clientcommands.aw index f29340aaf..c2bb3f5b4 100644 --- a/src/main/resources/clientcommands.aw +++ b/src/main/resources/clientcommands.aw @@ -12,3 +12,7 @@ accessible method net/minecraft/client/Minecraft openChatScreen (Ljava/lang/Stri accessible method net/minecraft/network/chat/HoverEvent (Lnet/minecraft/network/chat/HoverEvent$TypedHoverEvent;)V accessible class net/minecraft/network/chat/HoverEvent$TypedHoverEvent + +accessible field net/minecraft/network/ConnectionProtocol flows Ljava/util/Map; +accessible field net/minecraft/network/ConnectionProtocol$CodecData packetSet Lnet/minecraft/network/ConnectionProtocol$PacketSet; +accessible field net/minecraft/network/ConnectionProtocol$PacketSet classToId Lit/unimi/dsi/fastutil/objects/Object2IntMap; From f90f4895e8197a4ff1c9e3f27117f35f40275d14 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Mon, 19 Feb 2024 21:36:10 +0100 Subject: [PATCH 04/15] Make calls to MappingsHelper blocking --- .../features/MappingsHelper.java | 95 ++++++++++++------- 1 file changed, 63 insertions(+), 32 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 86ec68401..6b34eb5c8 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -29,6 +29,7 @@ import java.time.Duration; import java.util.Collection; import java.util.Optional; +import java.util.concurrent.CountDownLatch; public class MappingsHelper { @@ -48,6 +49,8 @@ public class MappingsHelper { private static final boolean IS_DEV_ENV = FabricLoader.getInstance().isDevelopmentEnvironment(); + private static CountDownLatch latch = null; + public static void initMappings(String version) { try { Files.createDirectories(MAPPINGS_DIR); @@ -56,6 +59,7 @@ public static void initMappings(String version) { } try (BufferedReader reader = Files.newBufferedReader(MAPPINGS_DIR.resolve(version + ".txt"))) { MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + latch = new CountDownLatch(0); } catch (IOException e) { mojmapOfficial.reset(); HttpClient httpClient = HttpClient.newHttpClient(); @@ -64,9 +68,10 @@ public static void initMappings(String version) { .GET() .timeout(Duration.ofSeconds(5)) .build(); + latch = new CountDownLatch(1); httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) - .thenAccept(versionsBody -> { + .thenCompose(versionsBody -> { JsonObject versionsJson = JsonParser.parseString(versionsBody).getAsJsonObject(); String versionUrl = versionsJson.getAsJsonArray("versions").asList().stream() .map(JsonElement::getAsJsonObject) @@ -79,37 +84,38 @@ public static void initMappings(String version) { .GET() .timeout(Duration.ofSeconds(5)) .build(); - httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenAccept(versionBody -> { - JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); - String mappingsUrl = versionJson - .getAsJsonObject("downloads") - .getAsJsonObject("client_mappings") - .get("url").getAsString(); - - HttpRequest mappingsRequest = HttpRequest.newBuilder() - .uri(URI.create(mappingsUrl)) - .GET() - .timeout(Duration.ofSeconds(5)) - .build(); - httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()) - .thenApply(HttpResponse::body) - .thenAccept(body -> { - try (StringReader reader = new StringReader(body)) { - MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); - } catch (IOException ex) { - LOGGER.error("Could not read ProGuard mappings file", ex); - ListenCommand.isEnabled = false; - } - try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { - writer.write(body); - } catch (IOException ex) { - LOGGER.error("Could not write ProGuard mappings file", ex); - } - }); - }); - }); + return httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()); + }) + .thenApply(HttpResponse::body) + .thenCompose(versionBody -> { + JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); + String mappingsUrl = versionJson + .getAsJsonObject("downloads") + .getAsJsonObject("client_mappings") + .get("url").getAsString(); + + HttpRequest mappingsRequest = HttpRequest.newBuilder() + .uri(URI.create(mappingsUrl)) + .GET() + .timeout(Duration.ofSeconds(5)) + .build(); + return httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()); + }) + .thenApply(HttpResponse::body) + .thenAccept(body -> { + try (StringReader reader = new StringReader(body)) { + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + } catch (IOException ex) { + LOGGER.error("Could not read ProGuard mappings file", ex); + ListenCommand.isEnabled = false; + } + try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { + writer.write(body); + } catch (IOException ex) { + LOGGER.error("Could not write ProGuard mappings file", ex); + } + }) + .thenRun(() -> latch.countDown()); } try (InputStream stream = FabricLoader.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny")) { @@ -124,10 +130,12 @@ public static void initMappings(String version) { } public static Collection mojmapClasses() { + block(); return mojmapOfficial.getClasses(); } public static Optional mojmapToOfficial_class(String mojmapClass) { + block(); MappingTree.ClassMapping officialClass = mojmapOfficial.getClass(mojmapClass); if (officialClass == null) { return Optional.empty(); @@ -136,6 +144,7 @@ public static Optional mojmapToOfficial_class(String mojmapClass) { } public static Optional officialToMojmap_class(String officialClass) { + block(); MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass, SRC_OFFICIAL); if (mojmapClass == null) { return Optional.empty(); @@ -144,6 +153,7 @@ public static Optional officialToMojmap_class(String officialClass) { } public static Optional mojmapToNamed_class(String mojmapClass) { + block(); Optional officialClass = mojmapToOfficial_class(mojmapClass); if (officialClass.isEmpty()) { return Optional.empty(); @@ -156,6 +166,7 @@ public static Optional mojmapToNamed_class(String mojmapClass) { } public static Optional namedToMojmap_class(String namedClass) { + block(); MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); if (officialClass == null) { return Optional.empty(); @@ -168,6 +179,7 @@ public static Optional namedToMojmap_class(String namedClass) { } public static Optional mojmapToIntermediary_class(String mojmapClass) { + block(); Optional officialClass = mojmapToOfficial_class(mojmapClass); if (officialClass.isEmpty()) { return Optional.empty(); @@ -180,6 +192,7 @@ public static Optional mojmapToIntermediary_class(String mojmapClass) { } public static Optional intermediaryToMojmap_class(String intermediaryClass) { + block(); MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { return Optional.empty(); @@ -192,6 +205,7 @@ public static Optional intermediaryToMojmap_class(String intermediaryCla } public static Optional namedOrIntermediaryToMojmap_class(String namedOrIntermediaryClass) { + block(); if (IS_DEV_ENV) { return MappingsHelper.namedToMojmap_class(namedOrIntermediaryClass); } @@ -199,6 +213,7 @@ public static Optional namedOrIntermediaryToMojmap_class(String namedOrI } public static Optional mojmapToNamedOrIntermediary_class(String mojmapClass) { + block(); if (IS_DEV_ENV) { return MappingsHelper.mojmapToNamed_class(mojmapClass); } @@ -206,6 +221,7 @@ public static Optional mojmapToNamedOrIntermediary_class(String mojmapCl } public static Optional officialToMojmap_field(String officialClass, String officialField) { + block(); MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass, officialField, null); if (mojmapField == null) { return Optional.empty(); @@ -214,6 +230,7 @@ public static Optional officialToMojmap_field(String officialClass, Stri } public static Optional namedToMojmap_field(String namedClass, String namedField) { + block(); MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); if (officialClass == null) { return Optional.empty(); @@ -230,6 +247,7 @@ public static Optional namedToMojmap_field(String namedClass, String nam } public static Optional intermediaryToMojmap_field(String intermediaryClass, String intermediaryField) { + block(); MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { return Optional.empty(); @@ -246,9 +264,22 @@ public static Optional intermediaryToMojmap_field(String intermediaryCla } public static Optional namedOrIntermediaryToMojmap_field(String namedOrIntermediaryClass, String namedOrIntermediaryField) { + block(); if (IS_DEV_ENV) { return namedToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } return intermediaryToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } + + private static void block() { + if (latch == null) { + throw new IllegalStateException("Function called too early, defer calling functions in MappingsHelper"); + } + try { + latch.await(); + } catch (InterruptedException e) { + LOGGER.error("Thread was interrupted while waiting", e); + ListenCommand.isEnabled = false; + } + } } From c5d86d4367eb5e6b2600463fa80b8d200e0c1a4d Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Mon, 19 Feb 2024 23:02:11 +0100 Subject: [PATCH 05/15] Gracefully handle empty results --- .../clientcommands/command/ListenCommand.java | 24 ++++++++++--------- .../MojmapPacketClassArgumentType.java | 8 +++---- .../features/MappingsHelper.java | 5 ++++ .../clientcommands/features/PacketDumper.java | 22 ++++++++++++----- 4 files changed, 37 insertions(+), 22 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index edf69112e..83a4cf407 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -83,9 +83,6 @@ private static int add(FabricClientCommandSource source, Class { - String packetClassName = packet.getClass().getName().replace('.', '/'); - Optional mojmapPacketName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName); - String packetData; Component packetDataPreview; if (Configs.packetDumpMethod == Configs.PacketDumpMethod.BYTE_BUF) { @@ -108,7 +105,11 @@ private static int add(FabricClientCommandSource source, Class packetName.substring(packetName.lastIndexOf('/') + 1)).orElseThrow()).withStyle(s -> s + String packetClassName = packet.getClass().getName().replace('.', '/'); + String mojmapPacketName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName).orElse(packetClassName); + mojmapPacketName = mojmapPacketName.substring(mojmapPacketName.lastIndexOf('/') + 1); + + MutableComponent packetComponent = Component.literal(mojmapPacketName).withStyle(s -> s .withUnderlined(true) .withHoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, packetDataPreview)) .withClickEvent(new ClickEvent(ClickEvent.Action.COPY_TO_CLIPBOARD, packetData))); @@ -140,8 +141,9 @@ private static int list(FabricClientCommandSource source) { source.sendFeedback(Component.translatable("commands.clisten.list")); packets.forEach(packetClass -> { String packetClassName = packetClass.getName().replace('.', '/'); - Optional mojmapName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName); - source.sendFeedback(Component.literal(mojmapName.map(name -> name.substring(name.lastIndexOf('/' + 1))).orElseThrow())); + String mojmapName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName).orElse(packetClassName); + mojmapName = mojmapName.substring(mojmapName.lastIndexOf('/' + 1)); + source.sendFeedback(Component.literal(mojmapName)); }); } @@ -246,20 +248,20 @@ private static Component serializeInner(Object object, Set seen, int dep .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> { String fieldName = field.getName(); - Optional mojmapFieldName = MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName); + String mojmapFieldName = MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName).orElse(fieldName); try { field.setAccessible(true); - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(field.get(object), seen, depth + 1)); + return Component.literal(mojmapFieldName + '=').append(serialize(field.get(object), seen, depth + 1)); } catch (InaccessibleObjectException | ReflectiveOperationException e) { try { MethodHandles.Lookup implLookup = UnsafeUtils.getImplLookup(); if (implLookup == null) { - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); + return Component.literal(mojmapFieldName + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); } VarHandle varHandle = implLookup.findVarHandle(object.getClass(), fieldName, field.getType()); - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(serialize(varHandle.get(object), seen, depth + 1)); + return Component.literal(mojmapFieldName + '=').append(serialize(varHandle.get(object), seen, depth + 1)); } catch (ReflectiveOperationException ex) { - return Component.literal(mojmapFieldName.orElse(fieldName) + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); + return Component.literal(mojmapFieldName + '=').append(Component.translatable("commands.clisten.packetError").withStyle(ChatFormatting.DARK_RED)); } } }) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index 2e9f06c14..816d3d05b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -19,7 +19,6 @@ import java.util.Collection; import java.util.Map; import java.util.Objects; -import java.util.Optional; import java.util.concurrent.CompletableFuture; import java.util.stream.Collectors; @@ -32,10 +31,9 @@ public class MojmapPacketClassArgumentType implements ArgumentType>> mojmapPackets = Arrays.stream(ConnectionProtocol.values()) .flatMap(connectionProtocol -> connectionProtocol.flows.values().stream() .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream())) - .map(clazz -> { - Optional mojmapPacket = MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')); - return mojmapPacket.map(packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz)).orElse(null); - }) + .map(clazz -> MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')) + .map(packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz)) + .orElse(null)) .collect(Collectors.filtering(Objects::nonNull, Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue, (l, r) -> l))); public static MojmapPacketClassArgumentType packet() { diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 6b34eb5c8..5bf645ec4 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -86,6 +86,11 @@ public static void initMappings(String version) { .build(); return httpClient.sendAsync(versionRequest, HttpResponse.BodyHandlers.ofString()); }) + .whenComplete((result, exception) -> { + if (exception != null) { + ListenCommand.isEnabled = false; + } + }) .thenApply(HttpResponse::body) .thenCompose(versionBody -> { JsonObject versionJson = JsonParser.parseString(versionBody).getAsJsonObject(); diff --git a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java index ba8ebf33a..3a7546b6c 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java @@ -201,7 +201,10 @@ public void writeMap(Map map, Writer keyWriter, Writer valueW @Override public > void writeEnumSet(EnumSet enumSet, Class enumClass) { dump("enumSet", () -> { - writer.name("enumClass").value(MappingsHelper.namedOrIntermediaryToMojmap_class(enumClass.getName().replace('.', '/')).orElseThrow()); + String className = enumClass.getName().replace('.', '/'); + String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); + writer.name("enumClass").value(mojmapClassName); writer.name("size").value(enumSet.size()); writer.name("elements").beginArray(); for (final E element : enumSet) { @@ -356,10 +359,14 @@ public void writeVec3(Vec3 vec3) { @Override public @NotNull PacketDumpByteBuf writeEnum(Enum value) { - return dump("enum", () -> writer - .name("enum").value(MappingsHelper.namedOrIntermediaryToMojmap_class(value.getDeclaringClass().getName().replace('.', '/')).orElseThrow()) - .name("value").value(value.name()) - ); + return dump("enum", () -> { + String className = value.getDeclaringClass().getName().replace('.', '/'); + String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); + writer + .name("enum").value(mojmapClassName) + .name("value").value(value.name()); + }); } @Override @@ -676,7 +683,10 @@ public int writeCharSequence(CharSequence charSequence, Charset charset) { private void dumpValueClass(Object value) throws IOException { writer.name("valueClass"); if (value != null) { - writer.value(MappingsHelper.namedOrIntermediaryToMojmap_class(value.getClass().getName().replace('.', '/')).orElseThrow()); + String className = value.getClass().getName().replace('.', '/'); + String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); + writer.value(mojmapClassName); } else { writer.nullValue(); } From c24d8379442bfe13bf5b7b8240ccd80e2eb508dd Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Mon, 19 Feb 2024 23:59:12 +0100 Subject: [PATCH 06/15] Wrap mappings trees in CompletableFuture --- .../clientcommands/ClientCommands.java | 2 +- .../features/MappingsHelper.java | 120 ++++++++---------- 2 files changed, 51 insertions(+), 71 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index c14c6267f..b3831704c 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -88,7 +88,7 @@ public void onInitializeClient() { ItemGroupCommand.registerItemGroups(); - MappingsHelper.initMappings(DetectedVersion.BUILT_IN.getName()); + MappingsHelper.createMappingsDir(); } private static Set getCommands(CommandDispatcher dispatcher) { diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 5bf645ec4..aa52e5510 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -11,6 +11,8 @@ import net.fabricmc.mappingio.format.MappingFormat; import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; +import net.minecraft.DetectedVersion; +import net.minecraft.Util; import org.slf4j.Logger; import java.io.BufferedReader; @@ -29,7 +31,7 @@ import java.time.Duration; import java.util.Collection; import java.util.Optional; -import java.util.concurrent.CountDownLatch; +import java.util.concurrent.CompletableFuture; public class MappingsHelper { @@ -37,39 +39,23 @@ public class MappingsHelper { private static final Path MAPPINGS_DIR = ClientCommands.configDir.resolve("mappings"); - private static final MemoryMappingTree mojmapOfficial = new MemoryMappingTree(); - private static final int SRC_OFFICIAL = 0; - private static final int DEST_OFFICIAL = 0; - - private static final MemoryMappingTree officialIntermediaryNamed = new MemoryMappingTree(); - private static final int SRC_INTERMEDIARY = 0; - private static final int DEST_INTERMEDIARY = 0; - private static final int SRC_NAMED = 1; - private static final int DEST_NAMED = 1; - private static final boolean IS_DEV_ENV = FabricLoader.getInstance().isDevelopmentEnvironment(); - private static CountDownLatch latch = null; - - public static void initMappings(String version) { - try { - Files.createDirectories(MAPPINGS_DIR); - } catch (IOException e) { - LOGGER.error("Failed to create mappings dir", e); - } + private static final CompletableFuture mojmapOfficial = Util.make(() -> { + String version = DetectedVersion.BUILT_IN.getName(); + MemoryMappingTree tree = new MemoryMappingTree(); try (BufferedReader reader = Files.newBufferedReader(MAPPINGS_DIR.resolve(version + ".txt"))) { - MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); - latch = new CountDownLatch(0); + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, tree); + return CompletableFuture.completedFuture(tree); } catch (IOException e) { - mojmapOfficial.reset(); + tree.reset(); HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest versionsRequest = HttpRequest.newBuilder() .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")) .GET() .timeout(Duration.ofSeconds(5)) .build(); - latch = new CountDownLatch(1); - httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) + return httpClient.sendAsync(versionsRequest, HttpResponse.BodyHandlers.ofString()) .thenApply(HttpResponse::body) .thenCompose(versionsBody -> { JsonObject versionsJson = JsonParser.parseString(versionsBody).getAsJsonObject(); @@ -107,9 +93,9 @@ public static void initMappings(String version) { return httpClient.sendAsync(mappingsRequest, HttpResponse.BodyHandlers.ofString()); }) .thenApply(HttpResponse::body) - .thenAccept(body -> { + .thenApply(body -> { try (StringReader reader = new StringReader(body)) { - MappingReader.read(reader, MappingFormat.PROGUARD_FILE, mojmapOfficial); + MappingReader.read(reader, MappingFormat.PROGUARD_FILE, tree); } catch (IOException ex) { LOGGER.error("Could not read ProGuard mappings file", ex); ListenCommand.isEnabled = false; @@ -119,29 +105,46 @@ public static void initMappings(String version) { } catch (IOException ex) { LOGGER.error("Could not write ProGuard mappings file", ex); } - }) - .thenRun(() -> latch.countDown()); + return tree; + }); } + }); + private static final int SRC_OFFICIAL = 0; + private static final int DEST_OFFICIAL = 0; + private static final CompletableFuture officialIntermediaryNamed = Util.make(() -> { try (InputStream stream = FabricLoader.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny")) { if (stream == null) { throw new IOException("Could not find mappings.tiny"); } - MappingReader.read(new InputStreamReader(stream), IS_DEV_ENV ? MappingFormat.TINY_2_FILE : MappingFormat.TINY_FILE, officialIntermediaryNamed); + MemoryMappingTree tree = new MemoryMappingTree(); + MappingReader.read(new InputStreamReader(stream), IS_DEV_ENV ? MappingFormat.TINY_2_FILE : MappingFormat.TINY_FILE, tree); + return CompletableFuture.completedFuture(tree); } catch (IOException e) { LOGGER.error("Could not read mappings.tiny", e); ListenCommand.isEnabled = false; + return CompletableFuture.failedFuture(e); + } + }); + private static final int SRC_INTERMEDIARY = 0; + private static final int DEST_INTERMEDIARY = 0; + private static final int SRC_NAMED = 1; + private static final int DEST_NAMED = 1; + + public static void createMappingsDir() { + try { + Files.createDirectories(MAPPINGS_DIR); + } catch (IOException e) { + LOGGER.error("Failed to create mappings dir", e); } } public static Collection mojmapClasses() { - block(); - return mojmapOfficial.getClasses(); + return mojmapOfficial.join().getClasses(); } public static Optional mojmapToOfficial_class(String mojmapClass) { - block(); - MappingTree.ClassMapping officialClass = mojmapOfficial.getClass(mojmapClass); + MappingTree.ClassMapping officialClass = mojmapOfficial.join().getClass(mojmapClass); if (officialClass == null) { return Optional.empty(); } @@ -149,8 +152,7 @@ public static Optional mojmapToOfficial_class(String mojmapClass) { } public static Optional officialToMojmap_class(String officialClass) { - block(); - MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass, SRC_OFFICIAL); + MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass, SRC_OFFICIAL); if (mojmapClass == null) { return Optional.empty(); } @@ -158,12 +160,11 @@ public static Optional officialToMojmap_class(String officialClass) { } public static Optional mojmapToNamed_class(String mojmapClass) { - block(); Optional officialClass = mojmapToOfficial_class(mojmapClass); if (officialClass.isEmpty()) { return Optional.empty(); } - MappingTree.ClassMapping namedClass = officialIntermediaryNamed.getClass(officialClass.get()); + MappingTree.ClassMapping namedClass = officialIntermediaryNamed.join().getClass(officialClass.get()); if (namedClass == null) { return Optional.empty(); } @@ -171,12 +172,11 @@ public static Optional mojmapToNamed_class(String mojmapClass) { } public static Optional namedToMojmap_class(String namedClass) { - block(); - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(namedClass, SRC_NAMED); if (officialClass == null) { return Optional.empty(); } - MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass.getSrcName(), SRC_OFFICIAL); + MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass.getSrcName(), SRC_OFFICIAL); if (mojmapClass == null) { return Optional.empty(); } @@ -184,12 +184,11 @@ public static Optional namedToMojmap_class(String namedClass) { } public static Optional mojmapToIntermediary_class(String mojmapClass) { - block(); Optional officialClass = mojmapToOfficial_class(mojmapClass); if (officialClass.isEmpty()) { return Optional.empty(); } - MappingTree.ClassMapping intermediaryClass = officialIntermediaryNamed.getClass(officialClass.get()); + MappingTree.ClassMapping intermediaryClass = officialIntermediaryNamed.join().getClass(officialClass.get()); if (intermediaryClass == null) { return Optional.empty(); } @@ -197,12 +196,11 @@ public static Optional mojmapToIntermediary_class(String mojmapClass) { } public static Optional intermediaryToMojmap_class(String intermediaryClass) { - block(); - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { return Optional.empty(); } - MappingTree.ClassMapping mojmapClass = mojmapOfficial.getClass(officialClass.getSrcName(), SRC_OFFICIAL); + MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass.getSrcName(), SRC_OFFICIAL); if (mojmapClass == null) { return Optional.empty(); } @@ -210,7 +208,6 @@ public static Optional intermediaryToMojmap_class(String intermediaryCla } public static Optional namedOrIntermediaryToMojmap_class(String namedOrIntermediaryClass) { - block(); if (IS_DEV_ENV) { return MappingsHelper.namedToMojmap_class(namedOrIntermediaryClass); } @@ -218,7 +215,6 @@ public static Optional namedOrIntermediaryToMojmap_class(String namedOrI } public static Optional mojmapToNamedOrIntermediary_class(String mojmapClass) { - block(); if (IS_DEV_ENV) { return MappingsHelper.mojmapToNamed_class(mojmapClass); } @@ -226,8 +222,7 @@ public static Optional mojmapToNamedOrIntermediary_class(String mojmapCl } public static Optional officialToMojmap_field(String officialClass, String officialField) { - block(); - MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass, officialField, null); + MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass, officialField, null); if (mojmapField == null) { return Optional.empty(); } @@ -235,16 +230,15 @@ public static Optional officialToMojmap_field(String officialClass, Stri } public static Optional namedToMojmap_field(String namedClass, String namedField) { - block(); - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(namedClass, SRC_NAMED); if (officialClass == null) { return Optional.empty(); } - MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(namedClass, namedField, null, SRC_NAMED); + MappingTree.FieldMapping officialField = officialIntermediaryNamed.join().getField(namedClass, namedField, null, SRC_NAMED); if (officialField == null) { return Optional.empty(); } - MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); if (mojmapField == null) { return Optional.empty(); } @@ -252,16 +246,15 @@ public static Optional namedToMojmap_field(String namedClass, String nam } public static Optional intermediaryToMojmap_field(String intermediaryClass, String intermediaryField) { - block(); - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { return Optional.empty(); } - MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(intermediaryClass, intermediaryField, null, SRC_INTERMEDIARY); + MappingTree.FieldMapping officialField = officialIntermediaryNamed.join().getField(intermediaryClass, intermediaryField, null, SRC_INTERMEDIARY); if (officialField == null) { return Optional.empty(); } - MappingTree.FieldMapping mojmapField = mojmapOfficial.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); if (mojmapField == null) { return Optional.empty(); } @@ -269,22 +262,9 @@ public static Optional intermediaryToMojmap_field(String intermediaryCla } public static Optional namedOrIntermediaryToMojmap_field(String namedOrIntermediaryClass, String namedOrIntermediaryField) { - block(); if (IS_DEV_ENV) { return namedToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } return intermediaryToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } - - private static void block() { - if (latch == null) { - throw new IllegalStateException("Function called too early, defer calling functions in MappingsHelper"); - } - try { - latch.await(); - } catch (InterruptedException e) { - LOGGER.error("Thread was interrupted while waiting", e); - ListenCommand.isEnabled = false; - } - } } From 3723617ade5e30be6e4a5c9c5aa8ce3daf5f1641 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 00:36:06 +0100 Subject: [PATCH 07/15] Optional -> @Nullable + gracefully handle errors --- .../clientcommands/command/ListenCommand.java | 9 +- .../MojmapPacketClassArgumentType.java | 6 +- .../features/MappingsHelper.java | 186 ++++++++++++------ .../clientcommands/features/PacketDumper.java | 6 +- 4 files changed, 132 insertions(+), 75 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index 83a4cf407..94ecaa567 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -40,6 +40,7 @@ import java.util.Date; import java.util.HashSet; import java.util.Map; +import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -106,7 +107,7 @@ private static int add(FabricClientCommandSource source, Class s @@ -141,7 +142,7 @@ private static int list(FabricClientCommandSource source) { source.sendFeedback(Component.translatable("commands.clisten.list")); packets.forEach(packetClass -> { String packetClassName = packetClass.getName().replace('.', '/'); - String mojmapName = MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName).orElse(packetClassName); + String mojmapName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName), packetClassName); mojmapName = mojmapName.substring(mojmapName.lastIndexOf('/' + 1)); source.sendFeedback(Component.literal(mojmapName)); }); @@ -240,7 +241,7 @@ private static Component serializeInner(Object object, Set seen, int dep } String className = object.getClass().getName().replace(".", "/"); - String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + String mojmapClassName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(className), className); mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); MutableComponent component = Component.literal(mojmapClassName + '{'); @@ -248,7 +249,7 @@ private static Component serializeInner(Object object, Set seen, int dep .filter(field -> !Modifier.isStatic(field.getModifiers())) .map(field -> { String fieldName = field.getName(); - String mojmapFieldName = MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName).orElse(fieldName); + String mojmapFieldName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_field(className, fieldName), fieldName); try { field.setAccessible(true); return Component.literal(mojmapFieldName + '=').append(serialize(field.get(object), seen, depth + 1)); diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index 816d3d05b..6d66f1c3d 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -9,6 +9,7 @@ import com.mojang.brigadier.suggestion.SuggestionsBuilder; import net.earthcomputer.clientcommands.features.MappingsHelper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; +import net.minecraft.Optionull; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.chat.Component; @@ -31,9 +32,8 @@ public class MojmapPacketClassArgumentType implements ArgumentType>> mojmapPackets = Arrays.stream(ConnectionProtocol.values()) .flatMap(connectionProtocol -> connectionProtocol.flows.values().stream() .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream())) - .map(clazz -> MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')) - .map(packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz)) - .orElse(null)) + .map(clazz -> Optionull.map(MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')), + packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz))) .collect(Collectors.filtering(Objects::nonNull, Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue, (l, r) -> l))); public static MojmapPacketClassArgumentType packet() { diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index aa52e5510..e4b86ba5b 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -13,6 +13,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.minecraft.DetectedVersion; import net.minecraft.Util; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import java.io.BufferedReader; @@ -30,8 +31,8 @@ import java.nio.file.StandardOpenOption; import java.time.Duration; import java.util.Collection; -import java.util.Optional; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; public class MappingsHelper { @@ -43,12 +44,11 @@ public class MappingsHelper { private static final CompletableFuture mojmapOfficial = Util.make(() -> { String version = DetectedVersion.BUILT_IN.getName(); - MemoryMappingTree tree = new MemoryMappingTree(); try (BufferedReader reader = Files.newBufferedReader(MAPPINGS_DIR.resolve(version + ".txt"))) { + MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(reader, MappingFormat.PROGUARD_FILE, tree); return CompletableFuture.completedFuture(tree); } catch (IOException e) { - tree.reset(); HttpClient httpClient = HttpClient.newHttpClient(); HttpRequest versionsRequest = HttpRequest.newBuilder() .uri(URI.create("https://piston-meta.mojang.com/mc/game/version_manifest_v2.json")) @@ -94,6 +94,7 @@ public class MappingsHelper { }) .thenApply(HttpResponse::body) .thenApply(body -> { + MemoryMappingTree tree = new MemoryMappingTree(); try (StringReader reader = new StringReader(body)) { MappingReader.read(reader, MappingFormat.PROGUARD_FILE, tree); } catch (IOException ex) { @@ -112,18 +113,18 @@ public class MappingsHelper { private static final int SRC_OFFICIAL = 0; private static final int DEST_OFFICIAL = 0; - private static final CompletableFuture officialIntermediaryNamed = Util.make(() -> { + private static final MemoryMappingTree officialIntermediaryNamed = Util.make(() -> { try (InputStream stream = FabricLoader.class.getClassLoader().getResourceAsStream("mappings/mappings.tiny")) { if (stream == null) { throw new IOException("Could not find mappings.tiny"); } MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(new InputStreamReader(stream), IS_DEV_ENV ? MappingFormat.TINY_2_FILE : MappingFormat.TINY_FILE, tree); - return CompletableFuture.completedFuture(tree); + return tree; } catch (IOException e) { LOGGER.error("Could not read mappings.tiny", e); ListenCommand.isEnabled = false; - return CompletableFuture.failedFuture(e); + return null; } }); private static final int SRC_INTERMEDIARY = 0; @@ -139,129 +140,184 @@ public static void createMappingsDir() { } } - public static Collection mojmapClasses() { - return mojmapOfficial.join().getClasses(); + public static @Nullable Collection mojmapClasses() { + try { + return mojmapOfficial.get().getClasses(); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; + } } - public static Optional mojmapToOfficial_class(String mojmapClass) { - MappingTree.ClassMapping officialClass = mojmapOfficial.join().getClass(mojmapClass); + public static @Nullable String mojmapToOfficial_class(String mojmapClass) { + MappingTree.ClassMapping officialClass; + try { + officialClass = mojmapOfficial.get().getClass(mojmapClass); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; + } if (officialClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(officialClass.getDstName(DEST_OFFICIAL)); + return officialClass.getDstName(DEST_OFFICIAL); } - public static Optional officialToMojmap_class(String officialClass) { - MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass, SRC_OFFICIAL); + public static @Nullable String officialToMojmap_class(String officialClass) { + MappingTree.ClassMapping mojmapClass; + try { + mojmapClass = mojmapOfficial.get().getClass(officialClass, SRC_OFFICIAL); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; + } if (mojmapClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapClass.getSrcName()); + return mojmapClass.getSrcName(); } - public static Optional mojmapToNamed_class(String mojmapClass) { - Optional officialClass = mojmapToOfficial_class(mojmapClass); - if (officialClass.isEmpty()) { - return Optional.empty(); + public static @Nullable String mojmapToNamed_class(String mojmapClass) { + String officialClass = mojmapToOfficial_class(mojmapClass); + if (officialClass == null) { + return null; } - MappingTree.ClassMapping namedClass = officialIntermediaryNamed.join().getClass(officialClass.get()); + MappingTree.ClassMapping namedClass = officialIntermediaryNamed.getClass(officialClass); if (namedClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(namedClass.getDstName(DEST_NAMED)); + return namedClass.getDstName(DEST_NAMED); } - public static Optional namedToMojmap_class(String namedClass) { - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(namedClass, SRC_NAMED); + public static @Nullable String namedToMojmap_class(String namedClass) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); if (officialClass == null) { - return Optional.empty(); + return null; + } + MappingTree.ClassMapping mojmapClass; + try { + mojmapClass = mojmapOfficial.get().getClass(officialClass.getSrcName(), SRC_OFFICIAL); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; } - MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass.getSrcName(), SRC_OFFICIAL); if (mojmapClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapClass.getSrcName()); + return mojmapClass.getSrcName(); } - public static Optional mojmapToIntermediary_class(String mojmapClass) { - Optional officialClass = mojmapToOfficial_class(mojmapClass); - if (officialClass.isEmpty()) { - return Optional.empty(); + public static @Nullable String mojmapToIntermediary_class(String mojmapClass) { + String officialClass = mojmapToOfficial_class(mojmapClass); + if (officialClass == null) { + return null; } - MappingTree.ClassMapping intermediaryClass = officialIntermediaryNamed.join().getClass(officialClass.get()); + MappingTree.ClassMapping intermediaryClass = officialIntermediaryNamed.getClass(officialClass); if (intermediaryClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(intermediaryClass.getDstName(DEST_INTERMEDIARY)); + return intermediaryClass.getDstName(DEST_INTERMEDIARY); } - public static Optional intermediaryToMojmap_class(String intermediaryClass) { - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(intermediaryClass, SRC_INTERMEDIARY); + public static @Nullable String intermediaryToMojmap_class(String intermediaryClass) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { - return Optional.empty(); + return null; + } + MappingTree.ClassMapping mojmapClass; + try { + mojmapClass = mojmapOfficial.get().getClass(officialClass.getSrcName(), SRC_OFFICIAL); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; } - MappingTree.ClassMapping mojmapClass = mojmapOfficial.join().getClass(officialClass.getSrcName(), SRC_OFFICIAL); if (mojmapClass == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapClass.getSrcName()); + return mojmapClass.getSrcName(); } - public static Optional namedOrIntermediaryToMojmap_class(String namedOrIntermediaryClass) { + public static @Nullable String namedOrIntermediaryToMojmap_class(String namedOrIntermediaryClass) { if (IS_DEV_ENV) { return MappingsHelper.namedToMojmap_class(namedOrIntermediaryClass); } return MappingsHelper.intermediaryToMojmap_class(namedOrIntermediaryClass); } - public static Optional mojmapToNamedOrIntermediary_class(String mojmapClass) { + public static @Nullable String mojmapToNamedOrIntermediary_class(String mojmapClass) { if (IS_DEV_ENV) { return MappingsHelper.mojmapToNamed_class(mojmapClass); } return MappingsHelper.mojmapToIntermediary_class(mojmapClass); } - public static Optional officialToMojmap_field(String officialClass, String officialField) { - MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass, officialField, null); + public static @Nullable String officialToMojmap_field(String officialClass, String officialField) { + MappingTree.FieldMapping mojmapField; + try { + mojmapField = mojmapOfficial.get().getField(officialClass, officialField, null); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; + } if (mojmapField == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapField.getSrcName()); + return mojmapField.getSrcName(); } - public static Optional namedToMojmap_field(String namedClass, String namedField) { - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(namedClass, SRC_NAMED); + public static @Nullable String namedToMojmap_field(String namedClass, String namedField) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(namedClass, SRC_NAMED); if (officialClass == null) { - return Optional.empty(); + return null; } - MappingTree.FieldMapping officialField = officialIntermediaryNamed.join().getField(namedClass, namedField, null, SRC_NAMED); + MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(namedClass, namedField, null, SRC_NAMED); if (officialField == null) { - return Optional.empty(); + return null; + } + MappingTree.FieldMapping mojmapField; + try { + mojmapField = mojmapOfficial.get().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; } - MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); if (mojmapField == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapField.getSrcName()); + return mojmapField.getSrcName(); } - public static Optional intermediaryToMojmap_field(String intermediaryClass, String intermediaryField) { - MappingTree.ClassMapping officialClass = officialIntermediaryNamed.join().getClass(intermediaryClass, SRC_INTERMEDIARY); + public static @Nullable String intermediaryToMojmap_field(String intermediaryClass, String intermediaryField) { + MappingTree.ClassMapping officialClass = officialIntermediaryNamed.getClass(intermediaryClass, SRC_INTERMEDIARY); if (officialClass == null) { - return Optional.empty(); + return null; } - MappingTree.FieldMapping officialField = officialIntermediaryNamed.join().getField(intermediaryClass, intermediaryField, null, SRC_INTERMEDIARY); + MappingTree.FieldMapping officialField = officialIntermediaryNamed.getField(intermediaryClass, intermediaryField, null, SRC_INTERMEDIARY); if (officialField == null) { - return Optional.empty(); + return null; + } + MappingTree.FieldMapping mojmapField; + try { + mojmapField = mojmapOfficial.get().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; } - MappingTree.FieldMapping mojmapField = mojmapOfficial.join().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); if (mojmapField == null) { - return Optional.empty(); + return null; } - return Optional.ofNullable(mojmapField.getSrcName()); + return mojmapField.getSrcName(); } - public static Optional namedOrIntermediaryToMojmap_field(String namedOrIntermediaryClass, String namedOrIntermediaryField) { + public static @Nullable String namedOrIntermediaryToMojmap_field(String namedOrIntermediaryClass, String namedOrIntermediaryField) { if (IS_DEV_ENV) { return namedToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } diff --git a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java index 3a7546b6c..2b29d3606 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/PacketDumper.java @@ -202,7 +202,7 @@ public void writeMap(Map map, Writer keyWriter, Writer valueW public > void writeEnumSet(EnumSet enumSet, Class enumClass) { dump("enumSet", () -> { String className = enumClass.getName().replace('.', '/'); - String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + String mojmapClassName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(className), className); mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); writer.name("enumClass").value(mojmapClassName); writer.name("size").value(enumSet.size()); @@ -361,7 +361,7 @@ public void writeVec3(Vec3 vec3) { public @NotNull PacketDumpByteBuf writeEnum(Enum value) { return dump("enum", () -> { String className = value.getDeclaringClass().getName().replace('.', '/'); - String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + String mojmapClassName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(className), className); mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); writer .name("enum").value(mojmapClassName) @@ -684,7 +684,7 @@ private void dumpValueClass(Object value) throws IOException { writer.name("valueClass"); if (value != null) { String className = value.getClass().getName().replace('.', '/'); - String mojmapClassName = MappingsHelper.namedOrIntermediaryToMojmap_class(className).orElse(className); + String mojmapClassName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(className), className); mojmapClassName = mojmapClassName.substring(mojmapClassName.lastIndexOf('/') + 1); writer.value(mojmapClassName); } else { From 1868973eeecae02e5c2525fb126a7434eda7df01 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 00:48:00 +0100 Subject: [PATCH 08/15] Fail CompletableFuture on exception --- .../clientcommands/features/MappingsHelper.java | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index e4b86ba5b..1c2eea2c2 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -22,6 +22,7 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.StringReader; +import java.io.UncheckedIOException; import java.net.URI; import java.net.http.HttpClient; import java.net.http.HttpRequest; @@ -94,19 +95,21 @@ public class MappingsHelper { }) .thenApply(HttpResponse::body) .thenApply(body -> { - MemoryMappingTree tree = new MemoryMappingTree(); try (StringReader reader = new StringReader(body)) { + MemoryMappingTree tree = new MemoryMappingTree(); MappingReader.read(reader, MappingFormat.PROGUARD_FILE, tree); + return tree; } catch (IOException ex) { LOGGER.error("Could not read ProGuard mappings file", ex); ListenCommand.isEnabled = false; + throw new UncheckedIOException(ex); + } finally { + try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { + writer.write(body); + } catch (IOException ex) { + LOGGER.error("Could not write ProGuard mappings file", ex); + } } - try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { - writer.write(body); - } catch (IOException ex) { - LOGGER.error("Could not write ProGuard mappings file", ex); - } - return tree; }); } }); From d03000219f611c9bc91827b7b78f132914eed665 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 00:56:31 +0100 Subject: [PATCH 09/15] Extract exception handling to function --- .../features/MappingsHelper.java | 82 +++++-------------- 1 file changed, 19 insertions(+), 63 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 1c2eea2c2..07f191df5 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -12,6 +12,7 @@ import net.fabricmc.mappingio.tree.MappingTree; import net.fabricmc.mappingio.tree.MemoryMappingTree; import net.minecraft.DetectedVersion; +import net.minecraft.Optionull; import net.minecraft.Util; import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; @@ -144,24 +145,11 @@ public static void createMappingsDir() { } public static @Nullable Collection mojmapClasses() { - try { - return mojmapOfficial.get().getClasses(); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + return Optionull.map(getMojmapOfficial(), MemoryMappingTree::getClasses); } public static @Nullable String mojmapToOfficial_class(String mojmapClass) { - MappingTree.ClassMapping officialClass; - try { - officialClass = mojmapOfficial.get().getClass(mojmapClass); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.ClassMapping officialClass = Optionull.map(getMojmapOfficial(), tree -> tree.getClass(mojmapClass)); if (officialClass == null) { return null; } @@ -169,14 +157,7 @@ public static void createMappingsDir() { } public static @Nullable String officialToMojmap_class(String officialClass) { - MappingTree.ClassMapping mojmapClass; - try { - mojmapClass = mojmapOfficial.get().getClass(officialClass, SRC_OFFICIAL); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.ClassMapping mojmapClass = Optionull.map(getMojmapOfficial(), tree -> tree.getClass(officialClass, SRC_OFFICIAL)); if (mojmapClass == null) { return null; } @@ -200,14 +181,7 @@ public static void createMappingsDir() { if (officialClass == null) { return null; } - MappingTree.ClassMapping mojmapClass; - try { - mojmapClass = mojmapOfficial.get().getClass(officialClass.getSrcName(), SRC_OFFICIAL); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.ClassMapping mojmapClass = Optionull.map(getMojmapOfficial(), tree -> tree.getClass(officialClass.getSrcName(), SRC_OFFICIAL)); if (mojmapClass == null) { return null; } @@ -231,14 +205,7 @@ public static void createMappingsDir() { if (officialClass == null) { return null; } - MappingTree.ClassMapping mojmapClass; - try { - mojmapClass = mojmapOfficial.get().getClass(officialClass.getSrcName(), SRC_OFFICIAL); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.ClassMapping mojmapClass = Optionull.map(getMojmapOfficial(), tree -> tree.getClass(officialClass.getSrcName(), SRC_OFFICIAL)); if (mojmapClass == null) { return null; } @@ -260,14 +227,7 @@ public static void createMappingsDir() { } public static @Nullable String officialToMojmap_field(String officialClass, String officialField) { - MappingTree.FieldMapping mojmapField; - try { - mojmapField = mojmapOfficial.get().getField(officialClass, officialField, null); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.FieldMapping mojmapField = Optionull.map(getMojmapOfficial(), tree -> tree.getField(officialClass, officialField, null)); if (mojmapField == null) { return null; } @@ -283,14 +243,7 @@ public static void createMappingsDir() { if (officialField == null) { return null; } - MappingTree.FieldMapping mojmapField; - try { - mojmapField = mojmapOfficial.get().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.FieldMapping mojmapField = Optionull.map(getMojmapOfficial(), tree -> tree.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL)); if (mojmapField == null) { return null; } @@ -306,14 +259,7 @@ public static void createMappingsDir() { if (officialField == null) { return null; } - MappingTree.FieldMapping mojmapField; - try { - mojmapField = mojmapOfficial.get().getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL); - } catch (ExecutionException | InterruptedException e) { - LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; - return null; - } + MappingTree.FieldMapping mojmapField = Optionull.map(getMojmapOfficial(), tree -> tree.getField(officialClass.getSrcName(), officialField.getSrcName(), null, SRC_OFFICIAL)); if (mojmapField == null) { return null; } @@ -326,4 +272,14 @@ public static void createMappingsDir() { } return intermediaryToMojmap_field(namedOrIntermediaryClass, namedOrIntermediaryField); } + + private static MemoryMappingTree getMojmapOfficial() { + try { + return mojmapOfficial.get(); + } catch (ExecutionException | InterruptedException e) { + LOGGER.error("mojmap mappings were not available", e); + ListenCommand.isEnabled = false; + return null; + } + } } From eebbc292df2e73eb8d036e44f06de8d30dc82a72 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 01:05:10 +0100 Subject: [PATCH 10/15] Make sure mappings dir is created first --- .../clientcommands/ClientCommands.java | 2 +- .../features/MappingsHelper.java | 24 ++++++++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index b3831704c..c552051ce 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -88,7 +88,7 @@ public void onInitializeClient() { ItemGroupCommand.registerItemGroups(); - MappingsHelper.createMappingsDir(); + MappingsHelper.load(); } private static Set getCommands(CommandDispatcher dispatcher) { diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index 07f191df5..e60a24739 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -36,7 +36,13 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; -public class MappingsHelper { +public final class MappingsHelper { + + private MappingsHelper() { + } + + public static void load() { + } private static final Logger LOGGER = LogUtils.getLogger(); @@ -44,6 +50,14 @@ public class MappingsHelper { private static final boolean IS_DEV_ENV = FabricLoader.getInstance().isDevelopmentEnvironment(); + static { + try { + Files.createDirectories(MAPPINGS_DIR); + } catch (IOException e) { + LOGGER.error("Failed to create mappings dir", e); + } + } + private static final CompletableFuture mojmapOfficial = Util.make(() -> { String version = DetectedVersion.BUILT_IN.getName(); try (BufferedReader reader = Files.newBufferedReader(MAPPINGS_DIR.resolve(version + ".txt"))) { @@ -136,14 +150,6 @@ public class MappingsHelper { private static final int SRC_NAMED = 1; private static final int DEST_NAMED = 1; - public static void createMappingsDir() { - try { - Files.createDirectories(MAPPINGS_DIR); - } catch (IOException e) { - LOGGER.error("Failed to create mappings dir", e); - } - } - public static @Nullable Collection mojmapClasses() { return Optionull.map(getMojmapOfficial(), MemoryMappingTree::getClasses); } From e405edf03dec1820f1fe068f416d2e5ea334223a Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 16:17:25 +0100 Subject: [PATCH 11/15] Do not nest flatMap calls --- .../command/arguments/MojmapPacketClassArgumentType.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index 6d66f1c3d..966b762af 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -30,10 +30,10 @@ public class MojmapPacketClassArgumentType implements ArgumentType Component.translatable("commands.clisten.unknownPacket", packet)); private static final Map>> mojmapPackets = Arrays.stream(ConnectionProtocol.values()) - .flatMap(connectionProtocol -> connectionProtocol.flows.values().stream() - .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream())) + .flatMap(connectionProtocol -> connectionProtocol.flows.values().stream()) + .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream()) .map(clazz -> Optionull.map(MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')), - packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz))) + packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz))) .collect(Collectors.filtering(Objects::nonNull, Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue, (l, r) -> l))); public static MojmapPacketClassArgumentType packet() { From a2c281dbdbdaeca09aed5bd67285b7a16841c8f2 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 17:56:52 +0100 Subject: [PATCH 12/15] Minor rewrite --- .../command/arguments/MojmapPacketClassArgumentType.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index 966b762af..c7ed5c5ca 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -14,7 +14,6 @@ import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.Packet; -import org.apache.commons.lang3.tuple.Pair; import java.util.Arrays; import java.util.Collection; @@ -33,8 +32,10 @@ public class MojmapPacketClassArgumentType implements ArgumentType connectionProtocol.flows.values().stream()) .flatMap(codecData -> codecData.packetSet.classToId.keySet().stream()) .map(clazz -> Optionull.map(MappingsHelper.namedOrIntermediaryToMojmap_class(clazz.getName().replace('.', '/')), - packet -> Pair.of(packet.substring(packet.lastIndexOf('/') + 1), clazz))) - .collect(Collectors.filtering(Objects::nonNull, Collectors.toUnmodifiableMap(Pair::getKey, Pair::getValue, (l, r) -> l))); + packet -> Map.entry(packet.substring(packet.lastIndexOf('/') + 1), clazz))) + .filter(Objects::nonNull) + .distinct() + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); public static MojmapPacketClassArgumentType packet() { return new MojmapPacketClassArgumentType(); From ee2f4738ac3bf5694bcc4a2465366266a56527c4 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 18:03:04 +0100 Subject: [PATCH 13/15] Fix bug --- .../net/earthcomputer/clientcommands/command/ListenCommand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index 94ecaa567..bb1a77e69 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -143,7 +143,7 @@ private static int list(FabricClientCommandSource source) { packets.forEach(packetClass -> { String packetClassName = packetClass.getName().replace('.', '/'); String mojmapName = Objects.requireNonNullElse(MappingsHelper.namedOrIntermediaryToMojmap_class(packetClassName), packetClassName); - mojmapName = mojmapName.substring(mojmapName.lastIndexOf('/' + 1)); + mojmapName = mojmapName.substring(mojmapName.lastIndexOf('/') + 1); source.sendFeedback(Component.literal(mojmapName)); }); } From d9e998478040809f907b4f2976174fa4b6c6f205 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Tue, 20 Feb 2024 21:45:43 +0100 Subject: [PATCH 14/15] Use Util#toMap --- .../command/arguments/MojmapPacketClassArgumentType.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java index c7ed5c5ca..0eea25638 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/arguments/MojmapPacketClassArgumentType.java @@ -10,6 +10,7 @@ import net.earthcomputer.clientcommands.features.MappingsHelper; import net.fabricmc.fabric.api.client.command.v2.FabricClientCommandSource; import net.minecraft.Optionull; +import net.minecraft.Util; import net.minecraft.commands.SharedSuggestionProvider; import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.chat.Component; @@ -20,7 +21,6 @@ import java.util.Map; import java.util.Objects; import java.util.concurrent.CompletableFuture; -import java.util.stream.Collectors; public class MojmapPacketClassArgumentType implements ArgumentType>> { @@ -35,7 +35,7 @@ public class MojmapPacketClassArgumentType implements ArgumentType Map.entry(packet.substring(packet.lastIndexOf('/') + 1), clazz))) .filter(Objects::nonNull) .distinct() - .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue)); + .collect(Util.toMap()); public static MojmapPacketClassArgumentType packet() { return new MojmapPacketClassArgumentType(); From 856c51a703427581cab321c40b2d8223fb976711 Mon Sep 17 00:00:00 2001 From: Frederik van der Els Date: Sun, 3 Mar 2024 15:09:33 +0100 Subject: [PATCH 15/15] Implement requested changes --- .../clientcommands/ClientCommands.java | 5 +---- .../clientcommands/UnsafeUtils.java | 10 ++++----- .../clientcommands/command/ListenCommand.java | 21 ++++++++++++++++--- .../features/MappingsHelper.java | 8 +++---- .../assets/clientcommands/lang/en_us.json | 1 + 5 files changed, 29 insertions(+), 16 deletions(-) diff --git a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java index c552051ce..a4d947b63 100644 --- a/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java +++ b/src/main/java/net/earthcomputer/clientcommands/ClientCommands.java @@ -15,7 +15,6 @@ import net.fabricmc.fabric.api.client.networking.v1.ClientPlayNetworking; import net.fabricmc.fabric.api.client.rendering.v1.WorldRenderEvents; import net.fabricmc.loader.api.FabricLoader; -import net.minecraft.DetectedVersion; import net.minecraft.Util; import net.minecraft.client.Minecraft; import net.minecraft.commands.CommandBuildContext; @@ -165,9 +164,7 @@ public static void registerCommands(CommandDispatcher WeatherCommand.register(dispatcher); PluginsCommand.register(dispatcher); CGameModeCommand.register(dispatcher); - if (ListenCommand.isEnabled) { - ListenCommand.register(dispatcher); - } + ListenCommand.register(dispatcher); Calendar calendar = Calendar.getInstance(); boolean registerChatCommand = calendar.get(Calendar.MONTH) == Calendar.APRIL && calendar.get(Calendar.DAY_OF_MONTH) == 1; diff --git a/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java index 3090adb7b..863252886 100644 --- a/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java +++ b/src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java @@ -1,8 +1,8 @@ package net.earthcomputer.clientcommands; import com.mojang.logging.LogUtils; -import net.earthcomputer.clientcommands.command.ListenCommand; import net.minecraft.Util; +import org.jetbrains.annotations.Nullable; import org.slf4j.Logger; import sun.misc.Unsafe; @@ -19,7 +19,7 @@ private UnsafeUtils() { private static final Logger LOGGER = LogUtils.getLogger(); - private static final Unsafe UNSAFE = Util.make(() -> { + private static final @Nullable Unsafe UNSAFE = Util.make(() -> { try { final Field unsafeField = Unsafe.class.getDeclaredField("theUnsafe"); unsafeField.setAccessible(true); @@ -30,7 +30,7 @@ private UnsafeUtils() { } }); - private static final MethodHandles.Lookup IMPL_LOOKUP = Util.make(() -> { + private static final @Nullable MethodHandles.Lookup IMPL_LOOKUP = Util.make(() -> { try { //noinspection ConstantValue if (UNSAFE == null) { @@ -44,11 +44,11 @@ private UnsafeUtils() { } }); - public static Unsafe getUnsafe() { + public static @Nullable Unsafe getUnsafe() { return UNSAFE; } - public static MethodHandles.Lookup getImplLookup() { + public static @Nullable MethodHandles.Lookup getImplLookup() { return IMPL_LOOKUP; } } diff --git a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java index bb1a77e69..a08c6a3f5 100644 --- a/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java +++ b/src/main/java/net/earthcomputer/clientcommands/command/ListenCommand.java @@ -50,8 +50,13 @@ public class ListenCommand { - public static boolean isEnabled = true; + private static volatile boolean isEnabled = true; + public static void disable() { + isEnabled = false; + } + + private static final SimpleCommandExceptionType COMMAND_DISABLED_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.clisten.commandDisabled")); private static final SimpleCommandExceptionType ALREADY_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.clisten.add.failed")); private static final SimpleCommandExceptionType NOT_LISTENING_EXCEPTION = new SimpleCommandExceptionType(Component.translatable("commands.clisten.remove.failed")); @@ -76,6 +81,7 @@ public static void register(CommandDispatcher dispatc } private static int add(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + checkEnabled(); if (!packets.add(packetClass)) { throw ALREADY_LISTENING_EXCEPTION.create(); } @@ -126,6 +132,7 @@ private static int add(FabricClientCommandSource source, Class> packetClass) throws CommandSyntaxException { + checkEnabled(); if (!packets.remove(packetClass)) { throw NOT_LISTENING_EXCEPTION.create(); } @@ -134,7 +141,8 @@ private static int remove(FabricClientCommandSource source, Class seen, int depth) { try { if (depth <= Configs.maximumPacketFieldDepth && seen.add(object)) { diff --git a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java index e60a24739..0319a6978 100644 --- a/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java +++ b/src/main/java/net/earthcomputer/clientcommands/features/MappingsHelper.java @@ -90,7 +90,7 @@ public static void load() { }) .whenComplete((result, exception) -> { if (exception != null) { - ListenCommand.isEnabled = false; + ListenCommand.disable(); } }) .thenApply(HttpResponse::body) @@ -116,7 +116,7 @@ public static void load() { return tree; } catch (IOException ex) { LOGGER.error("Could not read ProGuard mappings file", ex); - ListenCommand.isEnabled = false; + ListenCommand.disable(); throw new UncheckedIOException(ex); } finally { try (BufferedWriter writer = Files.newBufferedWriter(MAPPINGS_DIR.resolve(version + ".txt"), StandardOpenOption.CREATE)) { @@ -141,7 +141,7 @@ public static void load() { return tree; } catch (IOException e) { LOGGER.error("Could not read mappings.tiny", e); - ListenCommand.isEnabled = false; + ListenCommand.disable(); return null; } }); @@ -284,7 +284,7 @@ private static MemoryMappingTree getMojmapOfficial() { return mojmapOfficial.get(); } catch (ExecutionException | InterruptedException e) { LOGGER.error("mojmap mappings were not available", e); - ListenCommand.isEnabled = false; + ListenCommand.disable(); return null; } } diff --git a/src/main/resources/assets/clientcommands/lang/en_us.json b/src/main/resources/assets/clientcommands/lang/en_us.json index 6dcef4a0a..322be7ce1 100644 --- a/src/main/resources/assets/clientcommands/lang/en_us.json +++ b/src/main/resources/assets/clientcommands/lang/en_us.json @@ -142,6 +142,7 @@ "commands.ckit.list": "Available kits: %s", "commands.ckit.list.empty": "No available kits", + "commands.clisten.commandDisabled": "The command was disabled, check your logs", "commands.clisten.unknownPacket": "Unknown packet %s", "commands.clisten.receivedPacket": "Received the following packet: %s", "commands.clisten.sentPacket": "Sent the following packet: %s",