mirror of
https://github.com/ViaVersion/ViaRewind-Legacy-Support.git
synced 2025-01-24 21:21:50 +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;
|
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.viarewind.legacysupport.versioninfo.VersionInformer;
|
||||||
import com.viaversion.viaversion.api.Via;
|
import com.viaversion.viaversion.api.Via;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.AreaEffectCloudListener;
|
import com.viaversion.viarewind.legacysupport.feature.AreaEffectCloudListener;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.BounceListener;
|
import com.viaversion.viarewind.legacysupport.feature.BounceListener;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.BrewingListener;
|
import com.viaversion.viarewind.legacysupport.feature.BrewingListener;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.ElytraListener;
|
import com.viaversion.viarewind.legacysupport.feature.ElytraListener;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.EnchantingListener;
|
import com.viaversion.viarewind.legacysupport.feature.EnchantingListener;
|
||||||
import com.viaversion.viarewind.legacysupport.listener.SoundListener;
|
import com.viaversion.viarewind.legacysupport.feature.SoundListener;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.configuration.file.FileConfiguration;
|
import org.bukkit.configuration.file.FileConfiguration;
|
||||||
@ -35,6 +35,8 @@ import org.bukkit.scheduler.BukkitRunnable;
|
|||||||
|
|
||||||
public class BukkitPlugin extends JavaPlugin {
|
public class BukkitPlugin extends JavaPlugin {
|
||||||
|
|
||||||
|
private ProtocolVersion serverProtocol;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
// Make the config file
|
// Make the config file
|
||||||
@ -49,7 +51,7 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
final ProtocolVersion serverProtocol = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
|
serverProtocol = Via.getAPI().getServerVersion().lowestSupportedProtocolVersion();
|
||||||
if (!serverProtocol.isKnown()) return;
|
if (!serverProtocol.isKnown()) return;
|
||||||
cancel();
|
cancel();
|
||||||
|
|
||||||
@ -89,4 +91,13 @@ public class BukkitPlugin extends JavaPlugin {
|
|||||||
}
|
}
|
||||||
}.runTaskTimer(this, 1L, 1L);
|
}.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/>.
|
* 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.Via;
|
||||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
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.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.Particle;
|
import org.bukkit.Particle;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.AreaEffectCloud;
|
import org.bukkit.entity.AreaEffectCloud;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.Via;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,9 +16,8 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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 com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
@ -28,11 +27,14 @@ import java.util.Arrays;
|
|||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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 class BoundingBoxFixer {
|
||||||
|
|
||||||
public static void fixLilyPad(final Logger logger, final ProtocolVersion serverVersion) {
|
public static void fixLilyPad(final Logger logger, final ProtocolVersion serverVersion) {
|
||||||
try {
|
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);
|
setBoundingBox(boundingBoxField.get(null), 0.0625, 0.0, 0.0625, 0.9375, 0.015625, 0.9375);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
@ -42,9 +44,9 @@ public class BoundingBoxFixer {
|
|||||||
|
|
||||||
public static void fixCarpet(final Logger logger, final ProtocolVersion serverVersion) {
|
public static void fixCarpet(final Logger logger, final ProtocolVersion serverVersion) {
|
||||||
try {
|
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);
|
setBoundingBox(boundingBoxField.get(0), 0.0D, -0.0000001D, 0.0D, 1.0D, 0.0000001D, 1.0D);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
logger.log(Level.SEVERE, "Could not fix carpet bounding box.", 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_16_4 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_16_4);
|
||||||
final boolean pre1_20_2 = serverVersion.olderThanOrEqualTo(ProtocolVersion.v1_20_2);
|
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 boundingBoxEastField = 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 boundingBoxWestField = 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 boundingBoxSouthField = 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 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(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);
|
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/>.
|
* 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.Via;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.Via;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.Via;
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
@ -16,13 +16,10 @@
|
|||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* 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.viaversion.api.Via;
|
||||||
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
import com.viaversion.viarewind.legacysupport.BukkitPlugin;
|
||||||
import com.viaversion.viarewind.legacysupport.injector.NMSReflection;
|
|
||||||
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
import com.viaversion.viaversion.api.protocol.version.ProtocolVersion;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
@ -42,6 +39,9 @@ import org.bukkit.event.player.PlayerPickupItemEvent;
|
|||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
|
|
||||||
|
import static com.viaversion.viarewind.legacysupport.util.ReflectionUtil.*;
|
||||||
|
import static com.viaversion.viarewind.legacysupport.util.NMSUtil.*;
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public class SoundListener implements Listener {
|
public class SoundListener implements Listener {
|
||||||
|
|
||||||
@ -131,7 +131,7 @@ public class SoundListener implements Listener {
|
|||||||
private static void playBlockPlaceSoundNMS(Player player, Block block) throws Exception {
|
private static void playBlockPlaceSoundNMS(Player player, Block block) throws Exception {
|
||||||
World world = block.getWorld();
|
World world = block.getWorld();
|
||||||
Object nmsWorld = world.getClass().getMethod("getHandle").invoke(world);
|
Object nmsWorld = world.getClass().getMethod("getHandle").invoke(world);
|
||||||
Class<?> blockPositionClass = NMSReflection.getBlockPositionClass();
|
Class<?> blockPositionClass = getBlockPositionClass();
|
||||||
Object blockPosition = null;
|
Object blockPosition = null;
|
||||||
|
|
||||||
if (blockPositionClass != null)
|
if (blockPositionClass != null)
|
||||||
@ -148,11 +148,11 @@ public class SoundListener implements Listener {
|
|||||||
Method getStepSound;
|
Method getStepSound;
|
||||||
final int serverProtocol = Via.getAPI().getServerVersion().lowestSupportedVersion();
|
final int serverProtocol = Via.getAPI().getServerVersion().lowestSupportedVersion();
|
||||||
if (serverProtocol > ProtocolVersion.v1_8.getVersion() && serverProtocol < ProtocolVersion.v1_12.getVersion()) {
|
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()) {
|
} 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
|
} else { // 1.14 - 1.16.5
|
||||||
getStepSound = ReflectionAPI.findRecursiveMethodOrNull(nmsBlock.getClass(), "getStepSound", blockData.getClass());
|
getStepSound = getMethod(nmsBlock.getClass(), "getStepSound", blockData.getClass());
|
||||||
}
|
}
|
||||||
if (getStepSound == null) {
|
if (getStepSound == null) {
|
||||||
Via.getPlatform().getLogger().severe("Could not find getStepSound method in " + nmsBlock.getClass().getName());
|
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);
|
Object soundEffect = soundEffectMethod.invoke(soundType);
|
||||||
float volume = (float) volumeMethod.invoke(soundType);
|
float volume = (float) volumeMethod.invoke(soundType);
|
||||||
float pitch = (float) pitchMethod.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;
|
volume = (volume + 1.0f) / 2.0f;
|
||||||
pitch *= 0.8;
|
pitch *= 0.8;
|
||||||
@ -197,7 +197,7 @@ public class SoundListener implements Listener {
|
|||||||
// 1.8.8 -> 1.16.5
|
// 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) {
|
private static void playSound(Player player, Object soundEffect, Object soundCategory, double x, double y, double z, float volume, float pitch) {
|
||||||
try {
|
try {
|
||||||
Object packet = NMSReflection.getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
|
Object packet = getGamePacketClass("PacketPlayOutNamedSoundEffect").getConstructor(
|
||||||
soundEffect.getClass(), soundCategory.getClass(),
|
soundEffect.getClass(), soundCategory.getClass(),
|
||||||
double.class, double.class, double.class,
|
double.class, double.class, double.class,
|
||||||
float.class, float.class
|
float.class, float.class
|
||||||
@ -209,7 +209,7 @@ public class SoundListener implements Listener {
|
|||||||
|
|
||||||
// Volume = 1
|
// Volume = 1
|
||||||
// Pitch = .8
|
// Pitch = .8
|
||||||
NMSReflection.sendPacket(player, packet);
|
sendPacket(player, packet);
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
ex.printStackTrace();
|
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