Skip to content

Commit

Permalink
Implement most requested changes
Browse files Browse the repository at this point in the history
  • Loading branch information
xpple committed Feb 18, 2024
1 parent 42af6e6 commit 00172ab
Show file tree
Hide file tree
Showing 10 changed files with 212 additions and 118 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,9 @@ public static void registerCommands(CommandDispatcher<FabricClientCommandSource>
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;
Expand Down
3 changes: 3 additions & 0 deletions src/main/java/net/earthcomputer/clientcommands/Configs.java
Original file line number Diff line number Diff line change
Expand Up @@ -121,4 +121,7 @@ public enum PacketDumpMethod {
REFLECTION,
BYTE_BUF,
}

@Config
public static int maximumPacketFieldDepth = 10;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<Field> getAllFields(Class<?> clazz) {
Stream.Builder<Field> builder = Stream.builder();
Class<?> targetClass = clazz;
Expand Down
36 changes: 28 additions & 8 deletions src/main/java/net/earthcomputer/clientcommands/UnsafeUtils.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<Class<Packet<?>>> packets = new HashSet<>();
private static final Logger LOGGER = LogUtils.getLogger();

private static PacketCallback callback;
private static final Set<Class<? extends Packet<?>>> packets = new HashSet<>();

private static final ThreadLocal<ReferenceSet<Object>> SEEN = ThreadLocal.withInitial(ReferenceOpenHashSet::new);
private static PacketCallback callback;

public static void register(CommandDispatcher<FabricClientCommandSource> dispatcher) {
dispatcher.register(literal("clisten")
Expand All @@ -62,7 +74,7 @@ public static void register(CommandDispatcher<FabricClientCommandSource> dispatc
.executes(ctx -> clear(ctx.getSource()))));
}

private static int add(FabricClientCommandSource source, Class<Packet<?>> packetClass) throws CommandSyntaxException {
private static int add(FabricClientCommandSource source, Class<? extends Packet<?>> packetClass) throws CommandSyntaxException {
if (!packets.add(packetClass)) {
throw ALREADY_LISTENING_EXCEPTION.create();
}
Expand All @@ -81,22 +93,22 @@ private static int add(FabricClientCommandSource source, Class<Packet<?>> 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)));
Expand All @@ -111,7 +123,7 @@ private static int add(FabricClientCommandSource source, Class<Packet<?>> packet
return Command.SINGLE_SUCCESS;
}

private static int remove(FabricClientCommandSource source, Class<Packet<?>> packetClass) throws CommandSyntaxException {
private static int remove(FabricClientCommandSource source, Class<? extends Packet<?>> packetClass) throws CommandSyntaxException {
if (!packets.remove(packetClass)) {
throw NOT_LISTENING_EXCEPTION.create();
}
Expand All @@ -129,7 +141,7 @@ private static int list(FabricClientCommandSource source) {
packets.forEach(packetClass -> {
String packetClassName = packetClass.getName().replace('.', '/');
Optional<String> 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()));
});
}

Expand All @@ -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<Object> 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<Object> seen, int depth) {
if (object == null) {
return Component.literal("null");
}
Expand All @@ -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);
Expand All @@ -198,33 +207,33 @@ 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) {
return Component.translationArg(registry.key().location());
}
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("}");
}

Expand All @@ -240,11 +249,15 @@ private static Component serializeInner(Object object) {
Optional<String> 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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Class<Packet<?>>> {
public class MojmapPacketClassArgumentType implements ArgumentType<Class<? extends Packet<?>>> {

private static final Collection<String> 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<String> 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<String, Class<? extends Packet<?>>> mojmapPackets = Arrays.stream(ConnectionProtocol.values())
.flatMap(connectionProtocol -> connectionProtocol.flows.values().stream()
.flatMap(codecData -> codecData.packetSet.classToId.keySet().stream()))
.map(clazz -> {
Optional<String> 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<Packet<?>> getPacket(final CommandContext<FabricClientCommandSource> context, final String name) {
return (Class<Packet<?>>) context.getArgument(name, Class.class);
public static Class<? extends Packet<?>> getPacket(final CommandContext<FabricClientCommandSource> context, final String name) {
return (Class<? extends Packet<?>>) context.getArgument(name, Class.class);
}

@Override
@SuppressWarnings("unchecked")
public Class<Packet<?>> parse(StringReader reader) throws CommandSyntaxException {
String packet = MOJMAP_PACKET_PREFIX + reader.readString();
Optional<String> mojmapPacketName = MappingsHelper.mojmapToNamedOrIntermediary_class(packet);
if (mojmapPacketName.isEmpty()) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
public Class<? extends Packet<?>> 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<Packet<?>>) Class.forName(packetClass);
} catch (ReflectiveOperationException e) {
throw CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownArgument().create();
String packet = reader.getString().substring(start, reader.getCursor());
Class<? extends Packet<?>> packetClass = mojmapPackets.get(packet);
if (packetClass == null) {
throw UNKNOWN_PACKET_EXCEPTION.create(packet);
}
return packetClass;
}

@Override
public <S> CompletableFuture<Suggestions> listSuggestions(CommandContext<S> context, SuggestionsBuilder builder) {
return SharedSuggestionProvider.suggest(mojmapPackets, builder);
return SharedSuggestionProvider.suggest(mojmapPackets.keySet(), builder);
}

@Override
Expand Down
Loading

0 comments on commit 00172ab

Please sign in to comment.