Remove Reflection

This commit is contained in:
2026-06-11 23:18:22 +02:00
parent 932732737d
commit 1590f8f0ee
12 changed files with 50 additions and 104 deletions
@@ -43,6 +43,8 @@ public class ClassPatcher {
/** Pre-computed set of targeted internal names for fast filtering. */ /** Pre-computed set of targeted internal names for fast filtering. */
private final Set<String> targets; private final Set<String> targets;
private final Set<String> targetsPublicConstructor;
public ClassPatcher(List<AccessWidenerEntry> entries) { public ClassPatcher(List<AccessWidenerEntry> entries) {
this.entries = entries; this.entries = entries;
this.targets = entries.stream() this.targets = entries.stream()
@@ -53,6 +55,10 @@ public class ClassPatcher {
return Stream.of(s, s.substring(0, index)); return Stream.of(s, s.substring(0, index));
}) })
.collect(Collectors.toSet()); .collect(Collectors.toSet());
this.targetsPublicConstructor = entries.stream()
.filter(entry -> entry.directive().equals("transitive-extendable"))
.map(AccessWidenerEntry::target)
.collect(Collectors.toSet());
} }
/** /**
@@ -66,7 +72,7 @@ public class ClassPatcher {
try { try {
ClassReader cr = new ClassReader(classBytes); ClassReader cr = new ClassReader(classBytes);
ClassWriter cw = new ClassWriter(cr, 0); ClassWriter cw = new ClassWriter(cr, 0);
cr.accept(new ClassTransformer(cw, className, entries), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); cr.accept(new ClassTransformer(cw, className, entries, targetsPublicConstructor.contains(className)), ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
return cw.toByteArray(); return cw.toByteArray();
} catch (Exception e) { } catch (Exception e) {
LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage()); LOG.warning("[AccessWidener] Failed to transform " + className + ": " + e.getMessage());
@@ -30,11 +30,13 @@ public class ClassTransformer extends ClassVisitor {
private final String internalName; private final String internalName;
private final List<AccessWidenerEntry> entries; private final List<AccessWidenerEntry> entries;
private final boolean appendPublicConstructor;
public ClassTransformer(ClassVisitor cv, String internalName, List<AccessWidenerEntry> entries) { public ClassTransformer(ClassVisitor cv, String internalName, List<AccessWidenerEntry> entries, boolean appendPublicConstructor) {
super(Opcodes.ASM9, cv); super(Opcodes.ASM9, cv);
this.internalName = internalName; this.internalName = internalName;
this.entries = entries; this.entries = entries;
this.appendPublicConstructor = appendPublicConstructor;
} }
@Override @Override
@@ -44,6 +46,13 @@ public class ClassTransformer extends ClassVisitor {
if (!e.targets(internalName) || !"class".equals(e.memberType())) continue; if (!e.targets(internalName) || !"class".equals(e.memberType())) continue;
newAccess = applyDirective(e.directive(), newAccess, false); newAccess = applyDirective(e.directive(), newAccess, false);
} }
if (appendPublicConstructor) {
MethodVisitor methodVisitor = visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
methodVisitor.visitMaxs(0, 0);
methodVisitor.visitCode();
methodVisitor.visitInsn(Opcodes.RETURN);
methodVisitor.visitEnd();
}
super.visit(version, newAccess, name, signature, superName, interfaces); super.visit(version, newAccess, name, signature, superName, interfaces);
} }
@@ -11,6 +11,3 @@ mutable field org/bukkit/craftbukkit/block/CraftBlockState world Lorg/bukkit/cra
# For TickManager # For TickManager
accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J accessible field net/minecraft/server/ServerTickRateManager remainingSprintTicks J
# Test
accessible field net/minecraft/core/registries/BuiltInRegistries BLOCK_ENTITY_TYPE Lnet/minecraft/core/Registry;
@@ -38,6 +38,7 @@ import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey; import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandMap; import org.bukkit.command.CommandMap;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.CraftServer;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler; import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener; import org.bukkit.event.Listener;
@@ -47,7 +48,6 @@ import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.persistence.PersistentDataType; import org.bukkit.persistence.PersistentDataType;
import java.lang.reflect.Field;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
@@ -73,19 +73,7 @@ public class BindCommand extends SWCommand implements Listener {
} }
} }
private static final CommandMap commandMap; private static final CommandMap commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap();
static {
Field knownCommandsField;
try {
knownCommandsField = Bukkit.getServer().getClass().getDeclaredField("commandMap");
knownCommandsField.setAccessible(true);
commandMap = (CommandMap) knownCommandsField.get(Bukkit.getServer());
} catch (IllegalAccessException | NoSuchFieldException var2) {
Bukkit.shutdown();
throw new SecurityException("Oh shit. Commands cannot be registered.", var2);
}
}
private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command"); private static final NamespacedKey KEY = SWUtils.getNamespaceKey("command");
@@ -1,36 +0,0 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2025 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar;
import jdk.internal.misc.Unsafe;
import lombok.experimental.UtilityClass;
@UtilityClass
public final class Reflection {
public static Object newInstance(Class<?> clazz) {
try {
return Unsafe.getUnsafe().allocateInstance(clazz);
} catch (InstantiationException e) {
throw new SecurityException("Could not create object", e);
}
}
}
@@ -22,35 +22,20 @@ package de.steamwar.command;
import lombok.experimental.UtilityClass; import lombok.experimental.UtilityClass;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.Command; import org.bukkit.command.Command;
import org.bukkit.command.CommandMap;
import org.bukkit.command.SimpleCommandMap; import org.bukkit.command.SimpleCommandMap;
import org.bukkit.craftbukkit.CraftServer;
import java.lang.reflect.Field;
import java.util.Map; import java.util.Map;
@UtilityClass @UtilityClass
class CommandRegistering { class CommandRegistering {
private static final CommandMap commandMap; private static final SimpleCommandMap commandMap;
private static final Map<String, Command> knownCommandMap; private static final Map<String, Command> knownCommandMap;
static { static {
try { commandMap = ((CraftServer) Bukkit.getServer()).getCommandMap();
final Field commandMapField = Bukkit.getServer().getClass().getDeclaredField("commandMap"); knownCommandMap = commandMap.getKnownCommands();
commandMapField.setAccessible(true);
commandMap = (CommandMap) commandMapField.get(Bukkit.getServer());
} catch (NoSuchFieldException | IllegalAccessException exception) {
Bukkit.shutdown();
throw new SecurityException("Oh shit. Commands cannot be registered.", exception);
}
try {
final Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands");
knownCommandsField.setAccessible(true);
knownCommandMap = (Map<String, Command>) knownCommandsField.get(commandMap);
} catch (NoSuchFieldException | IllegalAccessException exception) {
Bukkit.shutdown();
throw new SecurityException("Oh shit. Commands cannot be registered.", exception);
}
} }
static void unregister(Command command) { static void unregister(Command command) {
@@ -20,7 +20,6 @@
package de.steamwar.entity; package de.steamwar.entity;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import de.steamwar.Reflection;
import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntArrayList;
import lombok.Getter; import lombok.Getter;
import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.core.registries.BuiltInRegistries;
@@ -174,7 +173,7 @@ public class REntity {
} }
public void showAnimation(byte animation) { public void showAnimation(byte animation) {
ClientboundAnimatePacket packet = (ClientboundAnimatePacket) Reflection.newInstance(ClientboundAnimatePacket.class); ClientboundAnimatePacket packet = new ClientboundAnimatePacket();
packet.id = entityId; packet.id = entityId;
packet.action = animation; packet.action = animation;
server.updateEntity(this, packet); server.updateEntity(this, packet);
@@ -185,7 +184,7 @@ public class REntity {
} }
public void showDamage() { public void showDamage() {
ClientboundEntityEventPacket packet = (ClientboundEntityEventPacket) Reflection.newInstance(ClientboundEntityEventPacket.class); ClientboundEntityEventPacket packet = new ClientboundEntityEventPacket();
packet.entityId = entityId; packet.entityId = entityId;
packet.eventId = (byte) 2; packet.eventId = (byte) 2;
server.updateEntity(this, packet); server.updateEntity(this, packet);
@@ -363,7 +362,7 @@ public class REntity {
} }
private Object getHeadRotationPacket() { private Object getHeadRotationPacket() {
ClientboundRotateHeadPacket packet = (ClientboundRotateHeadPacket) Reflection.newInstance(ClientboundRotateHeadPacket.class); ClientboundRotateHeadPacket packet = new ClientboundRotateHeadPacket();
packet.entityId = entityId; packet.entityId = entityId;
packet.yHeadRot = headYaw; packet.yHeadRot = headYaw;
return packet; return packet;
@@ -33,8 +33,8 @@ import java.util.List;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
public class ChunkHider { public class ChunkHider {
private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new);
private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataShallowCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new);
private final int SECTION_SPAN_SIZE = 16; private final int SECTION_SPAN_SIZE = 16;
private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8; private final byte BIT_PER_BLOCK_INDIRECTION_LIMIT = 8;
@@ -20,41 +20,25 @@
package de.steamwar.techhider; package de.steamwar.techhider;
import com.google.common.primitives.Bytes; import com.google.common.primitives.Bytes;
import de.steamwar.Reflection;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
import java.lang.reflect.Array;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.BiFunction; import java.util.function.Supplier;
import java.util.function.UnaryOperator; import java.util.function.UnaryOperator;
public class ProtocolUtils { public class ProtocolUtils {
private ProtocolUtils() { private ProtocolUtils() {
} }
@Deprecated public static <T> UnaryOperator<T> shallowCloneGenerator(Class<T> clazz, Supplier<T> supplier) {
public static BiFunction<Object, UnaryOperator<Object>, Object> arrayCloneGenerator(Class<?> elementClass) {
return (array, worker) -> {
int length = Array.getLength(array);
Object result = Array.newInstance(elementClass, length);
for (int i = 0; i < length; i++) {
Array.set(result, i, worker.apply(Array.get(array, i)));
}
return result;
};
}
public static <T> UnaryOperator<T> shallowCloneGenerator(Class<T> clazz) {
BiConsumer<T, T> filler = shallowFill(clazz); BiConsumer<T, T> filler = shallowFill(clazz);
return source -> { return source -> {
T clone = (T) Reflection.newInstance(clazz); T clone = supplier.get();
filler.accept(source, clone); filler.accept(source, clone);
return clone; return clone;
}; };
@@ -40,8 +40,8 @@ import java.util.stream.Collectors;
public class ChunkHider { public class ChunkHider {
public static final ChunkHider impl = new ChunkHider(); public static final ChunkHider impl = new ChunkHider();
private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class); private static final UnaryOperator<ClientboundLevelChunkWithLightPacket> chunkPacketCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkWithLightPacket.class, ClientboundLevelChunkWithLightPacket::new);
private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class); private static final UnaryOperator<ClientboundLevelChunkPacketData> chunkDataCloner = ProtocolUtils.shallowCloneGenerator(ClientboundLevelChunkPacketData.class, ClientboundLevelChunkPacketData::new);
public BiFunction<Player, ClientboundLevelChunkWithLightPacket, ClientboundLevelChunkWithLightPacket> chunkHiderGenerator(TechHider techHider) { public BiFunction<Player, ClientboundLevelChunkWithLightPacket, ClientboundLevelChunkWithLightPacket> chunkHiderGenerator(TechHider techHider) {
return (p, packet) -> { return (p, packet) -> {
@@ -92,9 +92,9 @@ public class TechHider {
techhiders.forEach(TinyProtocol.instance::removeFilter); techhiders.forEach(TinyProtocol.instance::removeFilter);
} }
public static final UnaryOperator<ClientboundSectionBlocksUpdatePacket> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class); public static final UnaryOperator<ClientboundSectionBlocksUpdatePacket> multiBlockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundSectionBlocksUpdatePacket.class, ClientboundSectionBlocksUpdatePacket::new);
private static final UnaryOperator<ClientboundBlockUpdatePacket> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class); private static final UnaryOperator<ClientboundBlockUpdatePacket> blockChangeCloner = ProtocolUtils.shallowCloneGenerator(ClientboundBlockUpdatePacket.class, ClientboundBlockUpdatePacket::new);
private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) { private ClientboundBlockUpdatePacket blockChangeHider(Player p, ClientboundBlockUpdatePacket packet) {
switch (locationEvaluator.checkBlockPos(p, packet.getPos())) { switch (locationEvaluator.checkBlockPos(p, packet.getPos())) {
@@ -15,16 +15,22 @@ accessible field net/minecraft/server/MinecraftServer services Lnet/minecraft/se
mutable field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services; mutable field net/minecraft/server/MinecraftServer services Lnet/minecraft/server/Services;
# REntity # REntity
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundAnimatePacket
accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I
mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket id I
accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I accessible field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I
mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I mutable field net/minecraft/network/protocol/game/ClientboundAnimatePacket action I
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundEntityEventPacket
accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I
mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket entityId I
accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B accessible field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B
mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B mutable field net/minecraft/network/protocol/game/ClientboundEntityEventPacket eventId B
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundRotateHeadPacket
accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B
mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket yHeadRot B
## + TechHider ## + TechHider
@@ -32,9 +38,13 @@ accessible field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket
mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I mutable field net/minecraft/network/protocol/game/ClientboundRotateHeadPacket entityId I
# For ChunkHider # For ChunkHider
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket
accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData;
mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData; mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkWithLightPacket chunkData Lnet/minecraft/network/protocol/game/ClientboundLevelChunkPacketData;
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData
accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B
mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B mutable field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData buffer [B
accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List; accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacketData blockEntitiesData Ljava/util/List;
@@ -47,6 +57,8 @@ accessible field net/minecraft/network/protocol/game/ClientboundLevelChunkPacket
# For TechHider # For TechHider
accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I accessible field net/minecraft/network/protocol/game/ClientboundMoveEntityPacket entityId I
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket
accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos; accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket sectionPos Lnet/minecraft/core/SectionPos;
accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S
mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket positions [S
@@ -54,4 +66,6 @@ accessible field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpd
mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState; mutable field net/minecraft/network/protocol/game/ClientboundSectionBlocksUpdatePacket states [Lnet/minecraft/world/level/block/state/BlockState;
# For legacy/TechHider # For legacy/TechHider
## transitive-extendable means that a public no args constructor is added without any super initialization
transitive-extendable class net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket
mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState; mutable field net/minecraft/network/protocol/game/ClientboundBlockUpdatePacket blockState Lnet/minecraft/world/level/block/state/BlockState;