226 lines
9.2 KiB
Java
226 lines
9.2 KiB
Java
/*
|
|
* This file is part of ViaVersion - https://github.com/ViaVersion/ViaVersion
|
|
* Copyright (C) 2016-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.viaversion.bukkit.platform;
|
|
|
|
import com.google.common.base.Preconditions;
|
|
import com.viaversion.viaversion.api.Via;
|
|
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer;
|
|
import com.viaversion.viaversion.bukkit.util.NMSUtil;
|
|
import com.viaversion.viaversion.platform.LegacyViaInjector;
|
|
import com.viaversion.viaversion.platform.WrappedChannelInitializer;
|
|
import com.viaversion.viaversion.util.ReflectionUtil;
|
|
import io.netty.channel.Channel;
|
|
import io.netty.channel.ChannelFuture;
|
|
import io.netty.channel.ChannelHandler;
|
|
import io.netty.channel.ChannelInitializer;
|
|
import java.lang.reflect.Field;
|
|
import java.lang.reflect.Method;
|
|
import java.util.List;
|
|
import java.util.logging.Level;
|
|
import org.bukkit.Bukkit;
|
|
import org.bukkit.plugin.PluginDescriptionFile;
|
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
|
|
|
public class BukkitViaInjector extends LegacyViaInjector {
|
|
|
|
private static final boolean HAS_WORLD_VERSION_PROTOCOL_VERSION = PaperViaInjector.hasClass("net.minecraft.SharedConstants")
|
|
&& PaperViaInjector.hasClass("net.minecraft.WorldVersion")
|
|
&& !PaperViaInjector.hasClass("com.mojang.bridge.game.GameVersion");
|
|
|
|
@Override
|
|
public void inject() throws ReflectiveOperationException {
|
|
if (PaperViaInjector.PAPER_INJECTION_METHOD) {
|
|
PaperViaInjector.setPaperChannelInitializeListener();
|
|
return;
|
|
}
|
|
|
|
super.inject();
|
|
}
|
|
|
|
@Override
|
|
public void uninject() throws ReflectiveOperationException {
|
|
if (PaperViaInjector.PAPER_INJECTION_METHOD) {
|
|
PaperViaInjector.removePaperChannelInitializeListener();
|
|
return;
|
|
}
|
|
|
|
super.uninject();
|
|
}
|
|
|
|
@Override
|
|
public int getServerProtocolVersion() throws ReflectiveOperationException {
|
|
if (PaperViaInjector.PAPER_PROTOCOL_METHOD) {
|
|
//noinspection deprecation
|
|
return Bukkit.getUnsafe().getProtocolVersion();
|
|
}
|
|
|
|
return HAS_WORLD_VERSION_PROTOCOL_VERSION ? cursedProtocolDetection() : veryCursedProtocolDetection();
|
|
}
|
|
|
|
private int cursedProtocolDetection() throws ReflectiveOperationException {
|
|
// Get the version from SharedConstants.getWorldVersion().getProtocolVersion()
|
|
Class<?> sharedConstantsClass = Class.forName("net.minecraft.SharedConstants");
|
|
Class<?> worldVersionClass = Class.forName("net.minecraft.WorldVersion");
|
|
Method getWorldVersionMethod = null;
|
|
for (Method method : sharedConstantsClass.getDeclaredMethods()) {
|
|
if (method.getReturnType() == worldVersionClass && method.getParameterTypes().length == 0) {
|
|
getWorldVersionMethod = method;
|
|
break;
|
|
}
|
|
}
|
|
Preconditions.checkNotNull(getWorldVersionMethod, "Failed to get world version method");
|
|
|
|
Object worldVersion = getWorldVersionMethod.invoke(null);
|
|
for (Method method : worldVersionClass.getDeclaredMethods()) {
|
|
if (method.getReturnType() == int.class && method.getParameterTypes().length == 0) {
|
|
return (int) method.invoke(worldVersion);
|
|
}
|
|
}
|
|
throw new IllegalAccessException("Failed to find protocol version method in WorldVersion");
|
|
}
|
|
|
|
private int veryCursedProtocolDetection() throws ReflectiveOperationException {
|
|
// Time to go on a journey! The protocol version is hidden inside an int in ServerPing.ServerData, that is only set once the server has ticked once
|
|
// Grab a static instance of the server
|
|
Class<?> serverClazz = NMSUtil.nms("MinecraftServer", "net.minecraft.server.MinecraftServer");
|
|
Object server = ReflectionUtil.invokeStatic(serverClazz, "getServer");
|
|
Preconditions.checkNotNull(server, "Failed to get server instance");
|
|
|
|
// Grab the ping class and find the field to access it
|
|
Class<?> pingClazz = NMSUtil.nms(
|
|
"ServerPing",
|
|
"net.minecraft.network.protocol.status.ServerPing"
|
|
);
|
|
Object ping = null;
|
|
for (Field field : serverClazz.getDeclaredFields()) {
|
|
if (field.getType() == pingClazz) {
|
|
field.setAccessible(true);
|
|
ping = field.get(server);
|
|
break;
|
|
}
|
|
}
|
|
Preconditions.checkNotNull(ping, "Failed to get server ping");
|
|
|
|
// Now get the ServerData inside ServerPing
|
|
Class<?> serverDataClass = NMSUtil.nms(
|
|
"ServerPing$ServerData",
|
|
"net.minecraft.network.protocol.status.ServerPing$ServerData"
|
|
);
|
|
Object serverData = null;
|
|
for (Field field : pingClazz.getDeclaredFields()) {
|
|
if (field.getType() == serverDataClass) {
|
|
field.setAccessible(true);
|
|
serverData = field.get(ping);
|
|
break;
|
|
}
|
|
}
|
|
Preconditions.checkNotNull(serverData, "Failed to get server data");
|
|
|
|
// Get protocol version field
|
|
for (Field field : serverDataClass.getDeclaredFields()) {
|
|
if (field.getType() != int.class) {
|
|
continue;
|
|
}
|
|
|
|
field.setAccessible(true);
|
|
int protocolVersion = (int) field.get(serverData);
|
|
if (protocolVersion != -1) {
|
|
return protocolVersion;
|
|
}
|
|
}
|
|
throw new RuntimeException("Failed to get server");
|
|
}
|
|
|
|
@Override
|
|
protected @Nullable Object getServerConnection() throws ReflectiveOperationException {
|
|
Class<?> serverClass = NMSUtil.nms(
|
|
"MinecraftServer",
|
|
"net.minecraft.server.MinecraftServer"
|
|
);
|
|
Class<?> connectionClass = NMSUtil.nms(
|
|
"ServerConnection",
|
|
"net.minecraft.server.network.ServerConnection"
|
|
);
|
|
|
|
Object server = ReflectionUtil.invokeStatic(serverClass, "getServer");
|
|
for (Method method : serverClass.getDeclaredMethods()) {
|
|
if (method.getReturnType() != connectionClass || method.getParameterTypes().length != 0) {
|
|
continue;
|
|
}
|
|
|
|
// We need the method that initiates the connection if not yet set
|
|
Object connection = method.invoke(server);
|
|
if (connection != null) {
|
|
return connection;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
@Override
|
|
protected WrappedChannelInitializer createChannelInitializer(ChannelInitializer<Channel> oldInitializer) {
|
|
return new BukkitChannelInitializer(oldInitializer);
|
|
}
|
|
|
|
@Override
|
|
protected void blame(ChannelHandler bootstrapAcceptor) throws ReflectiveOperationException {
|
|
// Let's find who to blame!
|
|
ClassLoader classLoader = bootstrapAcceptor.getClass().getClassLoader();
|
|
if (classLoader.getClass().getName().equals("org.bukkit.plugin.java.PluginClassLoader")) {
|
|
PluginDescriptionFile description = ReflectionUtil.get(classLoader, "description", PluginDescriptionFile.class);
|
|
throw new RuntimeException("Unable to inject, due to " + bootstrapAcceptor.getClass().getName() + ", try without the plugin " + description.getName() + "?");
|
|
} else {
|
|
throw new RuntimeException("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public boolean lateProtocolVersionSetting() {
|
|
return !(PaperViaInjector.PAPER_PROTOCOL_METHOD || HAS_WORLD_VERSION_PROTOCOL_VERSION);
|
|
}
|
|
|
|
public boolean isBinded() {
|
|
if (PaperViaInjector.PAPER_INJECTION_METHOD) {
|
|
return true;
|
|
}
|
|
try {
|
|
Object connection = getServerConnection();
|
|
if (connection == null) {
|
|
return false;
|
|
}
|
|
|
|
for (Field field : connection.getClass().getDeclaredFields()) {
|
|
if (!List.class.isAssignableFrom(field.getType())) {
|
|
continue;
|
|
}
|
|
|
|
field.setAccessible(true);
|
|
List<?> value = (List<?>) field.get(connection);
|
|
// Check if the list has at least one element
|
|
synchronized (value) {
|
|
if (!value.isEmpty() && value.get(0) instanceof ChannelFuture) {
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
} catch (ReflectiveOperationException e) {
|
|
Via.getPlatform().getLogger().log(Level.SEVERE, "Failed to check if ViaVersion is binded", e);
|
|
}
|
|
return false;
|
|
}
|
|
} |