Skip to content

Commit

Permalink
use ReobfHelper for remapping field names
Browse files Browse the repository at this point in the history
  • Loading branch information
Brokkonaut committed Dec 4, 2021
1 parent 18c17d1 commit df5f4e3
Show file tree
Hide file tree
Showing 4 changed files with 203 additions and 20 deletions.
10 changes: 10 additions & 0 deletions nmsutils-v1_18_R1/pom.xml
Expand Up @@ -27,6 +27,12 @@
<version>${project.version}</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.fabricmc</groupId>
<artifactId>mapping-io</artifactId>
<version>0.3.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
<repositories>
<repository>
Expand All @@ -37,6 +43,10 @@
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>fabric-repo</id>
<url>https://maven.fabricmc.net/</url>
</repository>
</repositories>
<build>
<plugins>
Expand Down
Expand Up @@ -38,13 +38,11 @@
import org.bukkit.util.Vector;

public class EntityUtilsImpl implements EntityUtils {
private static final String FIELD_BAT_TARGET_NAME = "bW";
private static final Field FIELD_BAT_TARGET = ReobfHelper.getFieldByMojangName(net.minecraft.world.entity.ambient.Bat.class, "targetPosition"); // "bW";
private static final Field FIELD_ENTITY_TRACKER = ReobfHelper.getFieldByMojangName(Entity.class, "tracker"); // "tracker";

private final NMSUtilsImpl nmsUtils;

private Field fieldEntityTracker;
private Field fieldBatTarget;

public EntityUtilsImpl(NMSUtilsImpl nmsUtils) {
this.nmsUtils = nmsUtils;
}
Expand Down Expand Up @@ -133,23 +131,15 @@ public void setPiglinDancing(org.bukkit.entity.Entity piglin, boolean dancing) {
@Override
public void sendEntityPositionUpdate(org.bukkit.entity.Entity entity) {
Entity handle = ((CraftEntity) entity).getHandle();
if (fieldEntityTracker == null) {
try {
fieldEntityTracker = Entity.class.getDeclaredField("tracker");
fieldEntityTracker.setAccessible(true);
} catch (Exception e) {
nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not get tracker field", e);
}
}
try {
ChunkMap.TrackedEntity ete = (ChunkMap.TrackedEntity) fieldEntityTracker.get(handle);
ChunkMap.TrackedEntity ete = (ChunkMap.TrackedEntity) FIELD_ENTITY_TRACKER.get(handle);
if (ete != null) {
ClientboundTeleportEntityPacket positionPacket = new ClientboundTeleportEntityPacket(handle);
ete.seenBy.stream().forEach(viewer -> {
viewer.send(positionPacket);
});
}
} catch (Exception e) {
} catch (ReflectiveOperationException e) {
nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "Could not send teleport packet", e);
}
}
Expand Down Expand Up @@ -210,12 +200,8 @@ public float getEntityPitch(org.bukkit.entity.Entity e) {
public void setEntityNavigationTarget(org.bukkit.entity.Entity entity, Location target, double speed) {
if (entity instanceof Bat) {
try {
if (fieldBatTarget == null) {
fieldBatTarget = net.minecraft.world.entity.ambient.Bat.class.getDeclaredField(FIELD_BAT_TARGET_NAME);
fieldBatTarget.setAccessible(true);
}
fieldBatTarget.set(((CraftBat) entity).getHandle(), new BlockPos(target.getX(), target.getY(), target.getZ()));
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
FIELD_BAT_TARGET.set(((CraftBat) entity).getHandle(), new BlockPos(target.getX(), target.getY(), target.getZ()));
} catch (ReflectiveOperationException e) {
nmsUtils.getPlugin().getLogger().log(Level.SEVERE, "could not set field", e);
}
} else if (entity instanceof Vex) {
Expand Down
Expand Up @@ -17,6 +17,7 @@ public NMSUtilsImpl(Plugin plugin) {
this.entityUtilsImpl = new EntityUtilsImpl(this);
this.worldUtilsImpl = new WorldUtilsImpl(this);
this.miscUtilsImpl = new MiscUtilsImpl(this);
// Remapper.foo(net.minecraft.world.entity.ambient.Bat.class);
}

@Override
Expand Down
@@ -0,0 +1,186 @@
package de.cubeside.nmsutils.v1_18_R1;

import io.papermc.paper.util.ObfHelper;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import net.fabricmc.mappingio.MappingReader;
import net.fabricmc.mappingio.format.MappingFormat;
import net.fabricmc.mappingio.tree.MappingTree;
import net.fabricmc.mappingio.tree.MappingTree.FieldMapping;
import net.fabricmc.mappingio.tree.MemoryMappingTree;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.framework.qual.DefaultQualifier;

/**
* Modified from https://github.com/PaperMC/Paper/blob/675d1e3f58ab86d5a3b1bc8bbcf2beefc9696078/patches/server/0420-Deobfuscate-stacktraces-in-log-messages-crash-report.patch
* see there for the license
*/
@DefaultQualifier(NonNull.class)
public enum ReobfHelper {
INSTANCE;

public static final String MOJANG_PLUS_YARN_NAMESPACE = "mojang+yarn";
public static final String SPIGOT_NAMESPACE = "spigot";

private final @Nullable Map<String, ClassMapping> mappingsByObfName;
private final @Nullable Map<String, ClassMapping> mappingsByMojangName;

ReobfHelper() {
final @Nullable Set<ClassMapping> maps = loadMappingsIfPresent();
if (maps != null) {
this.mappingsByObfName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::obfName, map -> map));
this.mappingsByMojangName = maps.stream().collect(Collectors.toUnmodifiableMap(ClassMapping::mojangName, map -> map));
} else {
this.mappingsByObfName = null;
this.mappingsByMojangName = null;
}
}

public @Nullable Map<String, ClassMapping> mappingsByObfName() {
return this.mappingsByObfName;
}

public @Nullable Map<String, ClassMapping> mappingsByMojangName() {
return this.mappingsByMojangName;
}

/**
* Attempts to get the obf name for a given class by its Mojang name. Will
* return the input string if mappings are not present.
*
* @param fullyQualifiedMojangName
* fully qualified class name (dotted)
* @return mapped or original fully qualified (dotted) class name
*/
public String reobfClassName(final String fullyQualifiedMojangName) {
if (this.mappingsByMojangName == null) {
return fullyQualifiedMojangName;
}

final ClassMapping map = this.mappingsByMojangName.get(fullyQualifiedMojangName);
if (map == null) {
return fullyQualifiedMojangName;
}

return map.obfName();
}

/**
* Attempts to get the Mojang name for a given class by its obf name. Will
* return the input string if mappings are not present.
*
* @param fullyQualifiedObfName
* fully qualified class name (dotted)
* @return mapped or original fully qualified (dotted) class name
*/
public String deobfClassName(final String fullyQualifiedObfName) {
if (this.mappingsByObfName == null) {
return fullyQualifiedObfName;
}

final ClassMapping map = this.mappingsByObfName.get(fullyQualifiedObfName);
if (map == null) {
return fullyQualifiedObfName;
}

return map.mojangName();
}

private static @Nullable Set<ClassMapping> loadMappingsIfPresent() {
try (final @Nullable InputStream mappingsInputStream = ObfHelper.class.getClassLoader().getResourceAsStream("META-INF/mappings/reobf.tiny")) {
if (mappingsInputStream == null) {
return null;
}
final MemoryMappingTree tree = new MemoryMappingTree();
MappingReader.read(new InputStreamReader(mappingsInputStream, StandardCharsets.UTF_8), MappingFormat.TINY_2, tree);
final Set<ClassMapping> classes = new HashSet<>();

final StringPool pool = new StringPool();
for (final MappingTree.ClassMapping cls : tree.getClasses()) {
final Map<String, String> methods = new HashMap<>();
final Map<String, String> fields = new HashMap<>();

for (final MappingTree.MethodMapping methodMapping : cls.getMethods()) {
methods.put(
pool.string(methodOrFieldKey(
methodMapping.getName(MOJANG_PLUS_YARN_NAMESPACE),
methodMapping.getDesc(MOJANG_PLUS_YARN_NAMESPACE))),
pool.string(methodMapping.getName(SPIGOT_NAMESPACE)));
}
for (final FieldMapping fieldsMapping : cls.getFields()) {
fields.put(
pool.string(fieldsMapping.getName(MOJANG_PLUS_YARN_NAMESPACE)),
pool.string(fieldsMapping.getName(SPIGOT_NAMESPACE)));
}

final ClassMapping map = new ClassMapping(
cls.getName(SPIGOT_NAMESPACE).replace('/', '.'),
cls.getName(MOJANG_PLUS_YARN_NAMESPACE).replace('/', '.'),
Map.copyOf(methods), Map.copyOf(fields));
classes.add(map);
}

return Set.copyOf(classes);
} catch (final IOException ex) {
System.err.println("Failed to load mappings for stacktrace deobfuscation.");
ex.printStackTrace();
return null;
}
}

public static String methodOrFieldKey(final String obfName, final String obfDescriptor) {
return obfName + obfDescriptor;
}

private static final class StringPool {
private final Map<String, String> pool = new HashMap<>();

public String string(final String string) {
return this.pool.computeIfAbsent(string, Function.identity());
}
}

public record ClassMapping(
String obfName,
String mojangName,
Map<String, String> methodsByMojang,
Map<String, String> fieldsByMojang) {
}

final static Map<String, ReobfHelper.ClassMapping> mappings = ReobfHelper.INSTANCE.mappingsByObfName();

public static String getObfuscatedFieldName(Class<?> clazz, String field) {
if (mappings != null) {
ClassMapping classMapping = mappings.get(clazz.getName());
if (classMapping != null) {
String fieldName = classMapping.fieldsByMojang().get(field);
if (fieldName != null) {
return fieldName;
}
}
}
return field;
}

public static Field getFieldByMojangName(Class<?> clazz, String fieldName) {
try {
Field field = clazz.getField(getObfuscatedFieldName(clazz, fieldName));
if (field != null) {
field.setAccessible(true);
}
return field;
} catch (NoSuchFieldException | SecurityException e) {
throw new RuntimeException(e);
}
}
}

0 comments on commit df5f4e3

Please sign in to comment.