mirror of
https://github.com/ViaVersion/ViaRewind-Legacy-Support.git
synced 2025-01-09 19:17:34 +01:00
Rewrote reflection utilities to be failsafe, restructure project
This commit is contained in:
parent
74ad92d944
commit
0c82936698
@ -18,15 +18,15 @@
|
||||
|
||||
package com.viaversion.viarewind.legacysupport;
|
||||
|
||||
import com.viaversion.viarewind.legacysupport.injector.BoundingBoxFixer;
|
||||
import com.viaversion.viarewind.legacysupport.feature.BoundingBoxFixer;
|
||||
import com.viaversion.viarewind.legacysupport.versioninfo.VersionInformer;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viarewind.legacysupport.listener.AreaEffectCloudListener;
|
||||
import com.viaversion.viarewind.legacysupport.listener.BounceListener;
|
||||
import com.viaversion.viarewind.legacysupport.listener.BrewingListener;
|
||||
import com.viaversion.viarewind.legacysupport.listener.ElytraListener;
|
||||
import com.viaversion.viarewind.legacysupport.listener.EnchantingListener;
|
||||
import com.viaversion.viarewind.legacysupport.listener.SoundListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.AreaEffectCloudListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.BounceListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.BrewingListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.ElytraListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.EnchantingListener;
|
||||
import com.viaversion.viarewind.legacysupport.feature.SoundListener;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.configuration.file.FileConfiguration;
|
||||
@ -35,6 +35,8 @@ import org.bukkit.scheduler.BukkitRunnable;
|
||||
|
||||
public class BukkitPlugin extends JavaPlugin {
|
||||
|
||||
private ProtocolVersion serverProtocol;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// Make the config file
|
||||
@ -49,7 +51,7 @@ public class BukkitPlugin extends JavaPlugin {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
final ProtocolVersion serverProtocol = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
|
||||
serverProtocol = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
|
||||
if (!serverProtocol.isKnown()) return;
|
||||
cancel();
|
||||
|
||||
@ -89,4 +91,13 @@ public class BukkitPlugin extends JavaPlugin {
|
||||
}
|
||||
}.runTaskTimer(this, 1L, 1L);
|
||||
}
|
||||
|
||||
public ProtocolVersion getServerProtocol() {
|
||||
return serverProtocol;
|
||||
}
|
||||
|
||||
public static BukkitPlugin getInstance() {
|
||||
return getPlugin(BukkitPlugin.class);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
||||
@ -24,7 +24,6 @@ import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
import org.bukkit.Particle;
|
||||
import org.bukkit.World;
|
||||
import org.bukkit.entity.AreaEffectCloud;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,9 +16,8 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.injector;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viarewind.legacysupport.reflection.ReflectionAPI;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@ -28,11 +27,14 @@ import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.*;
|
||||
import static com.viaversion.viarewind.legacysupport.util.NMSUtil.*;
|
||||
|
||||
public class BoundingBoxFixer {
|
||||
|
||||
public static void fixLilyPad(final Logger logger, final ProtocolVersion serverVersion) {
|
||||
try {
|
||||
final Field boundingBoxField = ReflectionAPI.getFieldAccessible(NMSReflection.getNMSBlock("BlockWaterLily"), serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
|
||||
final Field boundingBoxField = getFieldAccessible(getNMSBlockClass("BlockWaterLily"), serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
|
||||
|
||||
setBoundingBox(boundingBoxField.get(null), 0.0625, 0.0, 0.0625, 0.9375, 0.015625, 0.9375);
|
||||
} catch (Exception ex) {
|
||||
@ -42,9 +44,9 @@ public class BoundingBoxFixer {
|
||||
|
||||
public static void fixCarpet(final Logger logger, final ProtocolVersion serverVersion) {
|
||||
try {
|
||||
final Class<?> blockCarpetClass = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4) ? NMSReflection.getNMSBlock("BlockCarpet") : NMSReflection.getNMSBlock("CarpetBlock");
|
||||
final Class<?> blockCarpetClass = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4) ? getNMSBlockClass("BlockCarpet") : getNMSBlockClass("CarpetBlock");
|
||||
|
||||
final Field boundingBoxField = ReflectionAPI.getFieldAccessible(blockCarpetClass, serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
|
||||
final Field boundingBoxField = getFieldAccessible(blockCarpetClass, serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2) ? "a" : "b");
|
||||
setBoundingBox(boundingBoxField.get(0), 0.0D, -0.0000001D, 0.0D, 1.0D, 0.0000001D, 1.0D);
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Could not fix carpet bounding box.", ex);
|
||||
@ -58,12 +60,12 @@ public class BoundingBoxFixer {
|
||||
final boolean pre1_16_4 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4);
|
||||
final boolean pre1_20_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2);
|
||||
|
||||
final Class<?> blockLadderClass = NMSReflection.getNMSBlock("BlockLadder");
|
||||
final Class<?> blockLadderClass = getNMSBlockClass("BlockLadder");
|
||||
|
||||
final Field boundingBoxEastField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "b" : pre1_13_2 ? "c" : pre1_16_4 ? "c" : pre1_20_2 ? "d" : "e");
|
||||
final Field boundingBoxWestField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "c" : pre1_13_2 ? "o" : pre1_16_4 ? "d" : pre1_20_2 ? "e" : "f");
|
||||
final Field boundingBoxSouthField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "d" : pre1_13_2 ? "p" : pre1_16_4 ? "e" : pre1_20_2 ? "f" : "g");
|
||||
final Field boundingBoxNorthField = ReflectionAPI.getFieldAccessible(blockLadderClass, pre1_12_2 ? "e" : pre1_13_2 ? "q" : pre1_16_4 ? "f" : pre1_20_2 ? "g" : "h");
|
||||
final Field boundingBoxEastField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "b" : pre1_13_2 ? "c" : pre1_16_4 ? "c" : pre1_20_2 ? "d" : "e");
|
||||
final Field boundingBoxWestField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "c" : pre1_13_2 ? "o" : pre1_16_4 ? "d" : pre1_20_2 ? "e" : "f");
|
||||
final Field boundingBoxSouthField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "d" : pre1_13_2 ? "p" : pre1_16_4 ? "e" : pre1_20_2 ? "f" : "g");
|
||||
final Field boundingBoxNorthField = getFieldAccessible(blockLadderClass, pre1_12_2 ? "e" : pre1_13_2 ? "q" : pre1_16_4 ? "f" : pre1_20_2 ? "g" : "h");
|
||||
|
||||
setBoundingBox(boundingBoxEastField.get(null), 0.0D, 0.0D, 0.0D, 0.125D, 1.0D, 1.0D);
|
||||
setBoundingBox(boundingBoxWestField.get(null), 0.875D, 0.0D, 0.0D, 1.0D, 1.0D, 1.0D);
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,13 +16,10 @@
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.listener;
|
||||
package com.viaversion.viarewind.legacysupport.feature;
|
||||
|
||||
import com.viaversion.viarewind.legacysupport.reflection.MethodSignature;
|
||||
import com.viaversion.viarewind.legacysupport.reflection.ReflectionAPI;
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
||||
import com.viaversion.viarewind.legacysupport.injector.NMSReflection;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.Location;
|
||||
@ -42,6 +39,9 @@ import org.bukkit.event.player.PlayerPickupItemEvent;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.*;
|
||||
import static com.viaversion.viarewind.legacysupport.util.NMSUtil.*;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SoundListener implements Listener {
|
||||
|
||||
@ -131,7 +131,7 @@ public class SoundListener implements Listener {
|
||||
private static void playBlockPlaceSoundNMS(Player player, Block block) throws Exception {
|
||||
World world = block.getWorld();
|
||||
Object nmsWorld = world.getClass().getMethod("getHandle").invoke(world);
|
||||
Class<?> blockPositionClass = NMSReflection.getBlockPositionClass();
|
||||
Class<?> blockPositionClass = getBlockPositionClass();
|
||||
Object blockPosition = null;
|
||||
|
||||
if (blockPositionClass != null)
|
||||
@ -148,11 +148,11 @@ public class SoundListener implements Listener {
|
||||
Method getStepSound;
|
||||
final int serverProtocol = Via.getAPI().getServerVersion().lowestSupportedVersion();
|
||||
if (serverProtocol > ProtocolVersion.v1_8.getVersion() && serverProtocol < ProtocolVersion.v1_12.getVersion()) {
|
||||
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "w");
|
||||
getStepSound = getMethod(nmsBlock.getClass(), "w");
|
||||
} else if (serverProtocol > ProtocolVersion.v1_10.getVersion() && serverProtocol < ProtocolVersion.v1_13.getVersion()) {
|
||||
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "getStepSound");
|
||||
getStepSound = getMethod(nmsBlock.getClass(), "getStepSound");
|
||||
} else { // 1.14 - 1.16.5
|
||||
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "getStepSound", blockData.getClass());
|
||||
getStepSound = getMethod(nmsBlock.getClass(), "getStepSound", blockData.getClass());
|
||||
}
|
||||
if (getStepSound == null) {
|
||||
Via.getPlatform().getLogger().severe("Could not find getStepSound method in " + nmsBlock.getClass().getName());
|
||||
@ -186,7 +186,7 @@ public class SoundListener implements Listener {
|
||||
Object soundEffect = soundEffectMethod.invoke(soundType);
|
||||
float volume = (float) volumeMethod.invoke(soundType);
|
||||
float pitch = (float) pitchMethod.invoke(soundType);
|
||||
Object soundCategory = Enum.valueOf(NMSReflection.getSoundCategoryClass(), "BLOCKS");
|
||||
Object soundCategory = Enum.valueOf(getSoundCategoryClass(), "BLOCKS");
|
||||
|
||||
volume = (volume + 1.0f) / 2.0f;
|
||||
pitch *= 0.8;
|
||||
@ -197,7 +197,7 @@ public class SoundListener implements Listener {
|
||||
// 1.8.8 -> 1.16.5
|
||||
private static void playSound(Player player, Object soundEffect, Object soundCategory, double x, double y, double z, float volume, float pitch) {
|
||||
try {
|
||||
Object packet = NMSReflection.getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
|
||||
Object packet = getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
|
||||
soundEffect.getClass(), soundCategory.getClass(),
|
||||
double.class, double.class, double.class,
|
||||
float.class, float.class
|
||||
@ -209,7 +209,7 @@ public class SoundListener implements Listener {
|
||||
|
||||
// Volume = 1
|
||||
// Pitch = .8
|
||||
NMSReflection.sendPacket(player, packet);
|
||||
sendPacket(player, packet);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
@ -1,141 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
|
||||
* Copyright (C) 2018-2024 ViaVersion and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.injector;
|
||||
|
||||
import com.viaversion.viaversion.api.Via;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class NMSReflection {
|
||||
private static final int PROTOCOL_1_17 = 755;
|
||||
private static int protocolVersion = -1;
|
||||
|
||||
private static String version;
|
||||
private static Field playerConnectionField;
|
||||
|
||||
public static String getVersion() {
|
||||
return version == null ? version = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3] : version;
|
||||
}
|
||||
|
||||
public static int getProtocolVersion() {
|
||||
return protocolVersion == -1 ? protocolVersion = Via.getAPI().getServerVersion().lowestSupportedVersion() : protocolVersion;
|
||||
}
|
||||
|
||||
public static Class<?> getBlockPositionClass() {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.core.BlockPosition");
|
||||
}
|
||||
return getLegacyNMSClass("BlockPosition");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getNMSBlock(String name) {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.world.level.block." + name);
|
||||
}
|
||||
return getLegacyNMSClass(name);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static Class getSoundCategoryClass() {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.sounds.SoundCategory");
|
||||
}
|
||||
return getLegacyNMSClass("SoundCategory");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getPacketClass() {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.network.protocol.Packet");
|
||||
}
|
||||
return getLegacyNMSClass("Packet");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getGamePacketClass(String packetType) {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.network.protocol.game." + packetType);
|
||||
}
|
||||
return getLegacyNMSClass(packetType);
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getPlayerConnectionClass() {
|
||||
try {
|
||||
if (getProtocolVersion() >= PROTOCOL_1_17) {
|
||||
return Class.forName("net.minecraft.server.network.PlayerConnection");
|
||||
}
|
||||
return getLegacyNMSClass("PlayerConnection");
|
||||
} catch (ClassNotFoundException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Class<?> getLegacyNMSClass(String name) throws ClassNotFoundException {
|
||||
return Class.forName("net.minecraft.server." + getVersion() + "." + name);
|
||||
}
|
||||
|
||||
public static void sendPacket(Player player, Object packet) throws ReflectiveOperationException {
|
||||
Object nmsPlayer = player.getClass().getMethod("getHandle").invoke(player);
|
||||
if (playerConnectionField == null) {
|
||||
playerConnectionField = Arrays.stream(nmsPlayer.getClass().getFields())
|
||||
.filter(field -> field.getType() == getPlayerConnectionClass()).findFirst()
|
||||
.orElseThrow(() -> new ReflectiveOperationException("Failed to find PlayerConnection field in EntityPlayer"));
|
||||
}
|
||||
Object playerConnection = playerConnectionField.get(nmsPlayer);
|
||||
Method sendPacket;
|
||||
try { // TODO find better way
|
||||
sendPacket = playerConnection.getClass().getDeclaredMethod("sendPacket", getPacketClass());
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
sendPacket = playerConnection.getClass().getDeclaredMethod("a", getPacketClass());
|
||||
} catch (Exception e2) {
|
||||
throw new ReflectiveOperationException("Failed to find sendPacket method in PlayerConnection");
|
||||
}
|
||||
}
|
||||
sendPacket.invoke(playerConnection, packet);
|
||||
}
|
||||
}
|
@ -1,61 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
|
||||
* Copyright (C) 2018-2024 ViaVersion and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.reflection;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Objects;
|
||||
import java.util.StringJoiner;
|
||||
|
||||
public class MethodSignature {
|
||||
private final String name;
|
||||
private final Class<?>[] parameterTypes;
|
||||
|
||||
private Class<?> returnType;
|
||||
|
||||
public MethodSignature(String name, Class<?>... parameterTypes) {
|
||||
this.name = name;
|
||||
this.parameterTypes = parameterTypes;
|
||||
}
|
||||
|
||||
public String name() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public Class<?>[] parameterTypes() {
|
||||
return parameterTypes;
|
||||
}
|
||||
|
||||
public Class<?> returnType() {
|
||||
return returnType;
|
||||
}
|
||||
|
||||
public MethodSignature withReturnType(Class<?> returnType) {
|
||||
Objects.requireNonNull(returnType);
|
||||
this.returnType = returnType;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return new StringJoiner(", ", MethodSignature.class.getSimpleName() + "[", "]")
|
||||
.add("name='" + name + "'")
|
||||
.add("parameterTypes=" + Arrays.toString(parameterTypes))
|
||||
.toString();
|
||||
}
|
||||
}
|
@ -1,113 +0,0 @@
|
||||
/*
|
||||
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
|
||||
* Copyright (C) 2018-2024 ViaVersion and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.viaversion.viarewind.legacysupport.reflection;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
public class ReflectionAPI {
|
||||
private static final Map<String, Field> fields = new HashMap<>();
|
||||
private static boolean staticFinalModificationBlocked;
|
||||
|
||||
static {
|
||||
try {
|
||||
Field.class.getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException ex) {
|
||||
staticFinalModificationBlocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Field getField(Class clazz, String fieldname) {
|
||||
String key = clazz.getName() + ":" + fieldname;
|
||||
Field field = null;
|
||||
if (fields.containsKey(key)) {
|
||||
field = fields.get(key);
|
||||
} else {
|
||||
try {
|
||||
field = clazz.getDeclaredField(fieldname);
|
||||
} catch (NoSuchFieldException ignored) {
|
||||
}
|
||||
fields.put(key, field);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
public static Field getFieldAccessible(Class clazz, String fieldname) {
|
||||
Field field = getField(clazz, fieldname);
|
||||
if (field != null) field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
|
||||
public static void setFieldNotFinal(Field field) {
|
||||
int modifiers = field.getModifiers();
|
||||
if (!Modifier.isFinal(modifiers)) return;
|
||||
|
||||
if (staticFinalModificationBlocked) {
|
||||
try {
|
||||
Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
|
||||
getDeclaredFields0.setAccessible(true);
|
||||
Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
|
||||
for (Field classField : fields) {
|
||||
if ("modifiers".equals(classField.getName())) {
|
||||
classField.setAccessible(true);
|
||||
classField.set(field, modifiers & ~Modifier.FINAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
} else {
|
||||
setValuePrintException(Field.class, field, "modifiers", modifiers & ~Modifier.FINAL);
|
||||
}
|
||||
}
|
||||
|
||||
public static void setValue(Class clazz, Object object, String fieldname, Object value, boolean isFinal) throws IllegalAccessException {
|
||||
Field field = getFieldAccessible(clazz, fieldname);
|
||||
if (isFinal) setFieldNotFinal(field);
|
||||
field.set(object, value);
|
||||
}
|
||||
|
||||
public static void setValue(Class clazz, Object object, String fieldname, Object value) throws IllegalAccessException {
|
||||
setValue(clazz, object, fieldname, value, false);
|
||||
}
|
||||
|
||||
public static void setValuePrintException(Class clazz, Object object, String fieldname, Object value) {
|
||||
try {
|
||||
setValue(clazz, object, fieldname, value);
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static Method findRecursiveMethodOrNull(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
|
||||
try {
|
||||
return clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) return null;
|
||||
return findRecursiveMethodOrNull(superClass, methodName, parameterTypes);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
|
||||
* Copyright (C) 2018-2024 ViaVersion and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viarewind.legacysupport.util;
|
||||
|
||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.failSafeGetClass;
|
||||
|
||||
public class NMSUtil {
|
||||
|
||||
public static String nmsVersionPackage;
|
||||
private static Field playerConnectionField;
|
||||
|
||||
static {
|
||||
nmsVersionPackage = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
|
||||
}
|
||||
|
||||
public static Class<?> getBlockPositionClass() {
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.core.BlockPosition");
|
||||
} else {
|
||||
return getLegacyNMSClass("BlockPosition");
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getNMSBlockClass(final String name) {
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.world.level.block." + name);
|
||||
} else {
|
||||
return getLegacyNMSClass(name);
|
||||
}
|
||||
}
|
||||
|
||||
public static Class getSoundCategoryClass() { // Bypass generics
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.sounds.SoundCategory");
|
||||
} else {
|
||||
return getLegacyNMSClass("SoundCategory");
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getPacketClass() {
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.network.protocol.Packet");
|
||||
} else {
|
||||
return getLegacyNMSClass("Packet");
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getGamePacketClass(final String packet) {
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.network.protocol.game." + packet);
|
||||
} else {
|
||||
return getLegacyNMSClass(packet);
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getPlayerConnectionClass() {
|
||||
if (BukkitPlugin.getInstance().getServerProtocol().newerThanOrEqualTo(ProtocolVersion.v1_17)) {
|
||||
return failSafeGetClass("net.minecraft.server.network.PlayerConnection");
|
||||
} else {
|
||||
return getLegacyNMSClass("PlayerConnection");
|
||||
}
|
||||
}
|
||||
|
||||
public static void sendPacket(final Player player, final Object packet) {
|
||||
Object nmsPlayer = null;
|
||||
try {
|
||||
player.getClass().getMethod("getHandle").invoke(player);
|
||||
} catch (InvocationTargetException | IllegalAccessException | NoSuchMethodException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get EntityPlayer from player", e);
|
||||
return;
|
||||
}
|
||||
|
||||
// Cache result as it never changes
|
||||
if (playerConnectionField == null) {
|
||||
final Class<?> playerConnection = getPlayerConnectionClass();
|
||||
for (Field field : nmsPlayer.getClass().getFields()) {
|
||||
if (field.getType() == playerConnection) {
|
||||
playerConnectionField = field;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If reflection failed, log and return
|
||||
if (playerConnectionField == null) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to find PlayerConnection field in EntityPlayer");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
Object playerConnection;
|
||||
try {
|
||||
playerConnection = playerConnectionField.get(nmsPlayer);
|
||||
} catch (IllegalAccessException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get PlayerConnection from EntityPlayer", e);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
final Method sendPacket = ReflectionUtil.findMethod(player.getClass(), new String[] {"sendPacket", "a"}, getPacketClass());
|
||||
sendPacket.invoke(playerConnection, packet);
|
||||
} catch (IllegalAccessException | InvocationTargetException | NullPointerException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to send packet to player", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> getLegacyNMSClass(final String name) {
|
||||
try {
|
||||
return Class.forName("net.minecraft.server." + nmsVersionPackage + "." + name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Could not find NMS class " + name + "! NMS version package: " + nmsVersionPackage, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,197 @@
|
||||
/*
|
||||
* This file is part of ViaRewind-Legacy-Support - https://github.com/ViaVersion/ViaRewind-Legacy-Support
|
||||
* Copyright (C) 2018-2024 ViaVersion and contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU 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 General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
package com.viaversion.viarewind.legacysupport.util;
|
||||
|
||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.logging.Level;
|
||||
|
||||
/**
|
||||
* Common reflection utilities for Java 8 and newer.
|
||||
*/
|
||||
public class ReflectionUtil {
|
||||
|
||||
private static final Map<String, Field> fieldCache = new HashMap<>();
|
||||
private static boolean staticFinalModificationBlocked;
|
||||
|
||||
static {
|
||||
try {
|
||||
Field.class.getDeclaredField("modifiers");
|
||||
} catch (NoSuchFieldException ex) {
|
||||
staticFinalModificationBlocked = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method findMethod(final Class<?> clazz, final String[] methodNames, final Class<?>... parameterTypes) {
|
||||
for (String methodName : methodNames) {
|
||||
final Method method = getMethod(clazz, methodName, parameterTypes);
|
||||
if (method != null) {
|
||||
return method;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Recursively search for a method in the class and its superclasses. Returns null if the method is not found.
|
||||
*
|
||||
* @param clazz The class to search in
|
||||
* @param methodName The name of the method
|
||||
* @param parameterTypes The parameter types of the method
|
||||
* @return The method if found, otherwise null
|
||||
*/
|
||||
public static Method getMethod(final Class<?> clazz, final String methodName, final Class<?>... parameterTypes) {
|
||||
try {
|
||||
return clazz.getDeclaredMethod(methodName, parameterTypes);
|
||||
} catch (NoSuchMethodException ex) {
|
||||
final Class<?> superClass = clazz.getSuperclass();
|
||||
if (superClass == null) return null;
|
||||
return getMethod(superClass, methodName, parameterTypes);
|
||||
}
|
||||
}
|
||||
|
||||
public static Field getFieldAndCache(final Class<?> clazz, final String fieldName) {
|
||||
final String key = clazz.getName() + ":" + fieldName;
|
||||
if (fieldCache.containsKey(key)) {
|
||||
return fieldCache.get(key);
|
||||
} else {
|
||||
final Field field = failSafeGetField(clazz, fieldName);
|
||||
fieldCache.put(key, field);
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets and field from the {@link #fieldCache} or directly and makes it accessible.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param fieldName The field name
|
||||
* @return The field
|
||||
*/
|
||||
public static Field getFieldAccessible(final Class<?> clazz, final String fieldName) {
|
||||
final Field field = getFieldAndCache(clazz, fieldName);
|
||||
if (field != null) {
|
||||
field.setAccessible(true);
|
||||
}
|
||||
return field;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field from a class, and print an error message if it fails.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param fieldName The field name
|
||||
* @return The field, or null if it fails
|
||||
*/
|
||||
public static Field failSafeGetField(final Class<?> clazz, final String fieldName) {
|
||||
try {
|
||||
return getFieldAccessible(clazz, fieldName);
|
||||
} catch (Exception e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get field " + fieldName + " in class " + clazz.getName(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void setValue(final Class<?> clazz, final Object object, final String name, final Object value, final boolean isFinal) throws IllegalAccessException {
|
||||
final Field field = getFieldAccessible(clazz, name);
|
||||
if (isFinal) {
|
||||
removeFinal(field);
|
||||
}
|
||||
field.set(object, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove the final modifier from a field.
|
||||
*
|
||||
* @param field The field
|
||||
*/
|
||||
public static void removeFinal(final Field field) {
|
||||
final int modifiers = field.getModifiers();
|
||||
if (!Modifier.isFinal(modifiers)) {
|
||||
// Non-finals don't need to be modified
|
||||
return;
|
||||
}
|
||||
if (!staticFinalModificationBlocked) {
|
||||
// Older Java versions allow us to modify the modifiers directly and remove
|
||||
// the final modifier
|
||||
failSafeSetValue(Field.class, field, "modifiers", modifiers & ~Modifier.FINAL);
|
||||
return;
|
||||
}
|
||||
|
||||
// Modern Java bypass
|
||||
try {
|
||||
final Method getDeclaredFields0 = Class.class.getDeclaredMethod("getDeclaredFields0", boolean.class);
|
||||
getDeclaredFields0.setAccessible(true);
|
||||
|
||||
final Field[] fields = (Field[]) getDeclaredFields0.invoke(Field.class, false);
|
||||
for (Field classField : fields) {
|
||||
if ("modifiers".equals(classField.getName())) {
|
||||
classField.setAccessible(true);
|
||||
classField.set(field, modifiers & ~Modifier.FINAL);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (ReflectiveOperationException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to remove final modifier from field " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unsafe wrapper for value setting which bypasses final checks.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param object The object
|
||||
* @param name The field name
|
||||
* @param value The value
|
||||
* @throws IllegalAccessException If the field cannot be accessed
|
||||
*/
|
||||
public static void setValue(final Class<?> clazz, final Object object, final String name, final Object value) throws IllegalAccessException {
|
||||
setValue(clazz, object, name, value, false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set a field value, and print an error message if it fails.
|
||||
*
|
||||
* @param clazz The class
|
||||
* @param object The object
|
||||
* @param name The field name
|
||||
* @param value The value
|
||||
*/
|
||||
public static void failSafeSetValue(final Class<?> clazz, final Object object, final String name, final Object value) {
|
||||
try {
|
||||
setValue(clazz, object, name, value);
|
||||
} catch (Exception e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to set value for field " + name + " in class " + clazz.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
public static Class<?> failSafeGetClass(final String name) {
|
||||
try {
|
||||
return Class.forName(name);
|
||||
} catch (ClassNotFoundException e) {
|
||||
BukkitPlugin.getInstance().getLogger().log(Level.SEVERE, "Failed to get class " + name, e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue
Block a user