2021-03-22 23:06:40 +01:00
/ *
* This file is part of ViaVersion - https : //github.com/ViaVersion/ViaVersion
* Copyright ( C ) 2016 - 2021 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/>.
* /
2021-04-26 20:52:34 +02:00
package com.viaversion.viaversion.bukkit.platform ;
2016-09-25 15:39:37 +02:00
2019-03-18 12:30:02 +01:00
import com.google.gson.JsonArray ;
import com.google.gson.JsonObject ;
2021-04-26 20:52:34 +02:00
import com.viaversion.viaversion.api.Via ;
import com.viaversion.viaversion.api.platform.ViaInjector ;
import com.viaversion.viaversion.bukkit.handlers.BukkitChannelInitializer ;
import com.viaversion.viaversion.bukkit.util.NMSUtil ;
import com.viaversion.viaversion.util.ConcurrentList ;
import com.viaversion.viaversion.util.ListWrapper ;
2021-04-27 13:41:39 +02:00
import com.viaversion.viaversion.util.Pair ;
2021-04-26 20:52:34 +02:00
import com.viaversion.viaversion.util.ReflectionUtil ;
2021-04-27 13:41:39 +02:00
import io.netty.channel.ChannelFuture ;
import io.netty.channel.ChannelHandler ;
import io.netty.channel.ChannelInitializer ;
import io.netty.channel.socket.SocketChannel ;
import org.bukkit.Bukkit ;
import org.bukkit.plugin.PluginDescriptionFile ;
2016-09-25 15:39:37 +02:00
import java.lang.reflect.Field ;
import java.lang.reflect.Method ;
2020-06-09 17:53:31 +02:00
import java.util.ArrayList ;
2020-06-22 17:45:10 +02:00
import java.util.Collection ;
2016-09-25 15:39:37 +02:00
import java.util.List ;
2021-04-12 20:11:05 +02:00
//TODO screams
2016-09-25 15:39:37 +02:00
public class BukkitViaInjector implements ViaInjector {
2020-06-09 17:53:31 +02:00
private final List < ChannelFuture > injectedFutures = new ArrayList < > ( ) ;
private final List < Pair < Field , Object > > injectedLists = new ArrayList < > ( ) ;
2016-09-25 15:39:37 +02:00
2020-12-08 19:15:55 +01:00
private boolean protocolLib ;
2016-09-25 15:39:37 +02:00
@Override
2016-09-25 21:05:58 +02:00
public void inject ( ) throws Exception {
2021-04-29 23:32:13 +02:00
if ( PaperViaInjector . PAPER_INJECTION_METHOD ) {
PaperViaInjector . setPaperChannelInitializeListener ( ) ;
return ;
}
2016-09-25 15:39:37 +02:00
try {
Object connection = getServerConnection ( ) ;
if ( connection = = null ) {
2016-09-25 21:05:58 +02:00
throw new Exception ( " We failed to find the core component 'ServerConnection', please file an issue on our GitHub. " ) ;
2016-09-25 15:39:37 +02:00
}
for ( Field field : connection . getClass ( ) . getDeclaredFields ( ) ) {
field . setAccessible ( true ) ;
2020-06-22 17:45:10 +02:00
Object value = field . get ( connection ) ;
2016-09-25 15:39:37 +02:00
if ( value instanceof List ) {
// Inject the list
List wrapper = new ListWrapper ( ( List ) value ) {
@Override
2017-05-22 14:38:22 +02:00
public void handleAdd ( Object o ) {
if ( o instanceof ChannelFuture ) {
try {
injectChannelFuture ( ( ChannelFuture ) o ) ;
} catch ( Exception e ) {
e . printStackTrace ( ) ;
2016-09-25 15:39:37 +02:00
}
}
}
} ;
injectedLists . add ( new Pair < > ( field , connection ) ) ;
field . set ( connection , wrapper ) ;
// Iterate through current list
synchronized ( wrapper ) {
for ( Object o : ( List ) value ) {
if ( o instanceof ChannelFuture ) {
injectChannelFuture ( ( ChannelFuture ) o ) ;
} else {
break ; // not the right list.
}
}
}
}
}
} catch ( Exception e ) {
2016-09-25 21:05:58 +02:00
Via . getPlatform ( ) . getLogger ( ) . severe ( " Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version. " ) ;
throw e ;
2016-09-25 15:39:37 +02:00
}
}
2016-09-25 21:05:58 +02:00
private void injectChannelFuture ( ChannelFuture future ) throws Exception {
2016-09-25 15:39:37 +02:00
try {
2016-10-26 18:34:09 +02:00
List < String > names = future . channel ( ) . pipeline ( ) . names ( ) ;
ChannelHandler bootstrapAcceptor = null ;
// Pick best
for ( String name : names ) {
ChannelHandler handler = future . channel ( ) . pipeline ( ) . get ( name ) ;
try {
ReflectionUtil . get ( handler , " childHandler " , ChannelInitializer . class ) ;
bootstrapAcceptor = handler ;
} catch ( Exception e ) {
// Not this one
}
}
// Default to first (Also allows blame to work)
if ( bootstrapAcceptor = = null ) {
bootstrapAcceptor = future . channel ( ) . pipeline ( ) . first ( ) ;
}
2016-09-25 15:39:37 +02:00
try {
ChannelInitializer < SocketChannel > oldInit = ReflectionUtil . get ( bootstrapAcceptor , " childHandler " , ChannelInitializer . class ) ;
2016-09-29 16:25:18 +02:00
ChannelInitializer newInit = new BukkitChannelInitializer ( oldInit ) ;
2016-09-25 15:39:37 +02:00
ReflectionUtil . set ( bootstrapAcceptor , " childHandler " , newInit ) ;
injectedFutures . add ( future ) ;
} catch ( NoSuchFieldException e ) {
// let's find who to blame!
ClassLoader cl = bootstrapAcceptor . getClass ( ) . getClassLoader ( ) ;
if ( cl . getClass ( ) . getName ( ) . equals ( " org.bukkit.plugin.java.PluginClassLoader " ) ) {
PluginDescriptionFile yaml = ReflectionUtil . get ( cl , " description " , PluginDescriptionFile . class ) ;
throw new Exception ( " Unable to inject, due to " + bootstrapAcceptor . getClass ( ) . getName ( ) + " , try without the plugin " + yaml . getName ( ) + " ? " ) ;
} else {
throw new Exception ( " Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor . getClass ( ) . getName ( ) ) ;
}
}
} catch ( Exception e ) {
2016-09-25 21:05:58 +02:00
Via . getPlatform ( ) . getLogger ( ) . severe ( " We failed to inject ViaVersion, have you got late-bind enabled with something else? " ) ;
throw e ;
2016-09-25 15:39:37 +02:00
}
}
@Override
2021-05-02 10:12:37 +02:00
public void uninject ( ) throws Exception {
2016-09-25 15:39:37 +02:00
// TODO: Uninject from players currently online to prevent protocol lib issues.
2021-05-02 10:12:37 +02:00
if ( PaperViaInjector . PAPER_INJECTION_METHOD ) {
PaperViaInjector . removePaperChannelInitializeListener ( ) ;
return ;
}
2016-09-25 15:39:37 +02:00
for ( ChannelFuture future : injectedFutures ) {
2016-11-13 14:34:22 +01:00
List < String > names = future . channel ( ) . pipeline ( ) . names ( ) ;
ChannelHandler bootstrapAcceptor = null ;
// Pick best
for ( String name : names ) {
ChannelHandler handler = future . channel ( ) . pipeline ( ) . get ( name ) ;
try {
ChannelInitializer < SocketChannel > oldInit = ReflectionUtil . get ( handler , " childHandler " , ChannelInitializer . class ) ;
if ( oldInit instanceof BukkitChannelInitializer ) {
bootstrapAcceptor = handler ;
}
} catch ( Exception e ) {
// Not this one
}
}
// Default to first
if ( bootstrapAcceptor = = null ) {
bootstrapAcceptor = future . channel ( ) . pipeline ( ) . first ( ) ;
}
2016-09-25 15:39:37 +02:00
try {
ChannelInitializer < SocketChannel > oldInit = ReflectionUtil . get ( bootstrapAcceptor , " childHandler " , ChannelInitializer . class ) ;
2016-09-29 16:25:18 +02:00
if ( oldInit instanceof BukkitChannelInitializer ) {
ReflectionUtil . set ( bootstrapAcceptor , " childHandler " , ( ( BukkitChannelInitializer ) oldInit ) . getOriginal ( ) ) ;
2016-09-25 15:39:37 +02:00
}
} catch ( Exception e ) {
2016-11-13 14:25:56 +01:00
Via . getPlatform ( ) . getLogger ( ) . severe ( " Failed to remove injection handler, reload won't work with connections, please reboot! " ) ;
2016-09-25 15:39:37 +02:00
}
}
injectedFutures . clear ( ) ;
for ( Pair < Field , Object > pair : injectedLists ) {
try {
Object o = pair . getKey ( ) . get ( pair . getValue ( ) ) ;
if ( o instanceof ListWrapper ) {
pair . getKey ( ) . set ( pair . getValue ( ) , ( ( ListWrapper ) o ) . getOriginalList ( ) ) ;
}
} catch ( IllegalAccessException e ) {
2016-11-13 14:25:56 +01:00
Via . getPlatform ( ) . getLogger ( ) . severe ( " Failed to remove injection, reload won't work with connections, please reboot! " ) ;
2016-09-25 15:39:37 +02:00
}
}
injectedLists . clear ( ) ;
}
2021-03-26 12:51:38 +01:00
@Override
public boolean lateProtocolVersionSetting ( ) {
return true ;
}
2016-09-25 15:39:37 +02:00
@Override
2016-09-25 21:05:58 +02:00
public int getServerProtocolVersion ( ) throws Exception {
2021-04-29 23:32:13 +02:00
if ( PaperViaInjector . PAPER_PROTOCOL_METHOD ) {
2021-05-15 16:42:38 +02:00
//noinspection deprecation
2021-04-12 20:11:05 +02:00
return Bukkit . getUnsafe ( ) . getProtocolVersion ( ) ;
}
2016-09-25 15:39:37 +02:00
try {
2016-09-26 14:50:20 +02:00
Class < ? > serverClazz = NMSUtil . nms ( " MinecraftServer " ) ;
2016-09-25 15:39:37 +02:00
Object server = ReflectionUtil . invokeStatic ( serverClazz , " getServer " ) ;
2016-09-26 14:50:20 +02:00
Class < ? > pingClazz = NMSUtil . nms ( " ServerPing " ) ;
2016-09-25 15:39:37 +02:00
Object ping = null ;
// Search for ping method
for ( Field f : serverClazz . getDeclaredFields ( ) ) {
if ( f . getType ( ) ! = null ) {
if ( f . getType ( ) . getSimpleName ( ) . equals ( " ServerPing " ) ) {
f . setAccessible ( true ) ;
ping = f . get ( server ) ;
}
}
}
if ( ping ! = null ) {
Object serverData = null ;
for ( Field f : pingClazz . getDeclaredFields ( ) ) {
if ( f . getType ( ) ! = null ) {
if ( f . getType ( ) . getSimpleName ( ) . endsWith ( " ServerData " ) ) {
f . setAccessible ( true ) ;
serverData = f . get ( ping ) ;
}
}
}
if ( serverData ! = null ) {
int protocolVersion = - 1 ;
for ( Field f : serverData . getClass ( ) . getDeclaredFields ( ) ) {
if ( f . getType ( ) ! = null ) {
if ( f . getType ( ) = = int . class ) {
f . setAccessible ( true ) ;
protocolVersion = ( int ) f . get ( serverData ) ;
}
}
}
if ( protocolVersion ! = - 1 ) {
return protocolVersion ;
}
}
}
} catch ( Exception e ) {
2016-09-25 21:05:58 +02:00
throw new Exception ( " Failed to get server " , e ) ;
2016-09-25 15:39:37 +02:00
}
2016-09-25 21:05:58 +02:00
throw new Exception ( " Failed to get server " ) ;
2016-09-25 15:39:37 +02:00
}
2016-09-26 18:57:36 +02:00
@Override
public String getEncoderName ( ) {
return " encoder " ;
}
2016-09-29 22:29:58 +02:00
@Override
public String getDecoderName ( ) {
2020-12-08 19:15:55 +01:00
return protocolLib ? " protocol_lib_decoder " : " decoder " ;
2016-09-29 22:29:58 +02:00
}
2016-09-25 15:39:37 +02:00
public static Object getServerConnection ( ) throws Exception {
2016-09-26 14:50:20 +02:00
Class < ? > serverClazz = NMSUtil . nms ( " MinecraftServer " ) ;
2016-09-25 15:39:37 +02:00
Object server = ReflectionUtil . invokeStatic ( serverClazz , " getServer " ) ;
Object connection = null ;
for ( Method m : serverClazz . getDeclaredMethods ( ) ) {
if ( m . getReturnType ( ) ! = null ) {
if ( m . getReturnType ( ) . getSimpleName ( ) . equals ( " ServerConnection " ) ) {
if ( m . getParameterTypes ( ) . length = = 0 ) {
connection = m . invoke ( server ) ;
}
}
}
}
return connection ;
}
public static boolean isBinded ( ) {
2021-04-29 23:32:13 +02:00
if ( PaperViaInjector . PAPER_INJECTION_METHOD ) return true ;
2016-09-25 15:39:37 +02:00
try {
Object connection = getServerConnection ( ) ;
if ( connection = = null ) {
return false ;
}
for ( Field field : connection . getClass ( ) . getDeclaredFields ( ) ) {
field . setAccessible ( true ) ;
final Object value = field . get ( connection ) ;
if ( value instanceof List ) {
// Inject the list
synchronized ( value ) {
for ( Object o : ( List ) value ) {
if ( o instanceof ChannelFuture ) {
return true ;
} else {
break ; // not the right list.
}
}
}
}
}
} catch ( Exception e ) {
}
return false ;
}
2019-03-18 12:30:02 +01:00
@Override
public JsonObject getDump ( ) {
JsonObject data = new JsonObject ( ) ;
// Generate information about current injections
JsonArray injectedChannelInitializers = new JsonArray ( ) ;
for ( ChannelFuture cf : injectedFutures ) {
JsonObject info = new JsonObject ( ) ;
info . addProperty ( " futureClass " , cf . getClass ( ) . getName ( ) ) ;
info . addProperty ( " channelClass " , cf . channel ( ) . getClass ( ) . getName ( ) ) ;
// Get information about the pipes for this channel future
JsonArray pipeline = new JsonArray ( ) ;
for ( String pipeName : cf . channel ( ) . pipeline ( ) . names ( ) ) {
JsonObject pipe = new JsonObject ( ) ;
pipe . addProperty ( " name " , pipeName ) ;
if ( cf . channel ( ) . pipeline ( ) . get ( pipeName ) ! = null ) {
pipe . addProperty ( " class " , cf . channel ( ) . pipeline ( ) . get ( pipeName ) . getClass ( ) . getName ( ) ) ;
try {
Object child = ReflectionUtil . get ( cf . channel ( ) . pipeline ( ) . get ( pipeName ) , " childHandler " , ChannelInitializer . class ) ;
pipe . addProperty ( " childClass " , child . getClass ( ) . getName ( ) ) ;
if ( child instanceof BukkitChannelInitializer ) {
pipe . addProperty ( " oldInit " , ( ( BukkitChannelInitializer ) child ) . getOriginal ( ) . getClass ( ) . getName ( ) ) ;
}
} catch ( Exception e ) {
// Don't display
}
}
// Add to the pipeline array
pipeline . add ( pipe ) ;
}
info . add ( " pipeline " , pipeline ) ;
// Add to the list
injectedChannelInitializers . add ( info ) ;
}
data . add ( " injectedChannelInitializers " , injectedChannelInitializers ) ;
// Generate information about lists we've injected into
JsonObject wrappedLists = new JsonObject ( ) ;
JsonObject currentLists = new JsonObject ( ) ;
try {
for ( Pair < Field , Object > pair : injectedLists ) {
Object list = pair . getKey ( ) . get ( pair . getValue ( ) ) ;
// Note down the current value (could be overridden by another plugin)
currentLists . addProperty ( pair . getKey ( ) . getName ( ) , list . getClass ( ) . getName ( ) ) ;
// Also if it's not overridden we can display what's inside our list (possibly another plugin)
if ( list instanceof ListWrapper ) {
wrappedLists . addProperty ( pair . getKey ( ) . getName ( ) , ( ( ListWrapper ) list ) . getOriginalList ( ) . getClass ( ) . getName ( ) ) ;
}
}
data . add ( " wrappedLists " , wrappedLists ) ;
data . add ( " currentLists " , currentLists ) ;
} catch ( Exception e ) {
// Ignored, fields won't be present
}
data . addProperty ( " binded " , isBinded ( ) ) ;
return data ;
}
2020-06-22 17:45:10 +02:00
public static void patchLists ( ) throws Exception {
2021-04-29 23:32:13 +02:00
if ( PaperViaInjector . PAPER_INJECTION_METHOD ) return ;
2020-06-22 17:45:10 +02:00
Object connection = getServerConnection ( ) ;
if ( connection = = null ) {
Via . getPlatform ( ) . getLogger ( ) . warning ( " We failed to find the core component 'ServerConnection', please file an issue on our GitHub. " ) ;
return ;
}
for ( Field field : connection . getClass ( ) . getDeclaredFields ( ) ) {
field . setAccessible ( true ) ;
Object value = field . get ( connection ) ;
if ( ! ( value instanceof List ) ) continue ;
if ( value instanceof ConcurrentList ) continue ;
ConcurrentList list = new ConcurrentList ( ) ;
list . addAll ( ( Collection ) value ) ;
field . set ( connection , list ) ;
}
}
2020-12-08 19:15:55 +01:00
public void setProtocolLib ( boolean protocolLib ) {
this . protocolLib = protocolLib ;
}
2020-06-22 17:45:10 +02:00
}