Merge remote-tracking branch 'origin/master' into dev

This commit is contained in:
Nassim Jahnke 2021-10-05 14:21:31 +02:00
commit b19ec959b3
No known key found for this signature in database
GPG Key ID: 6BE3B555EBC5982B
4 changed files with 162 additions and 33 deletions

View File

@ -7,7 +7,7 @@ on: [push, pull_request]
jobs: jobs:
build: build:
# Only run on PRs if the source branch is on a different repo. We do not need run everything twice. # Only run on PRs if the source branch is on a different repo. We do not need to run everything twice.
if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }} if: ${{ github.event_name != 'pull_request' || github.repository != github.event.pull_request.head.repo.full_name }}
runs-on: ubuntu-latest runs-on: ubuntu-latest
@ -18,7 +18,7 @@ jobs:
- name: Set up JDK 11 - name: Set up JDK 11
uses: actions/setup-java@v2 uses: actions/setup-java@v2
with: with:
distribution: 'adopt' distribution: 'temurin'
java-version: 11 java-version: 11
- name: Build with Gradle - name: Build with Gradle
run: ./gradlew build run: ./gradlew build

View File

@ -17,46 +17,58 @@
*/ */
package com.viaversion.viaversion.bungee.platform; package com.viaversion.viaversion.bungee.platform;
import com.google.gson.JsonArray;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import com.viaversion.viaversion.api.Via; import com.viaversion.viaversion.api.Via;
import com.viaversion.viaversion.api.platform.ViaInjector; import com.viaversion.viaversion.api.platform.ViaInjector;
import com.viaversion.viaversion.bungee.handlers.BungeeChannelInitializer; import com.viaversion.viaversion.bungee.handlers.BungeeChannelInitializer;
import com.viaversion.viaversion.compatibility.ForcefulFieldModifier;
import com.viaversion.viaversion.compatibility.unsafe.UnsafeBackedForcefulFieldModifier;
import com.viaversion.viaversion.util.ReflectionUtil; import com.viaversion.viaversion.util.ReflectionUtil;
import com.viaversion.viaversion.util.SetWrapper;
import io.netty.channel.Channel; import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelInitializer;
import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet; import it.unimi.dsi.fastutil.ints.IntLinkedOpenHashSet;
import it.unimi.dsi.fastutil.ints.IntSortedSet; import it.unimi.dsi.fastutil.ints.IntSortedSet;
import net.md_5.bungee.api.ProxyServer;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Set;
public class BungeeViaInjector implements ViaInjector { public class BungeeViaInjector implements ViaInjector {
private final ForcefulFieldModifier forcefulFieldModifier; private static final Field LISTENERS_FIELD;
private final List<Channel> injectedChannels = new ArrayList<>();
public BungeeViaInjector() { static {
try { try {
this.forcefulFieldModifier = new UnsafeBackedForcefulFieldModifier(); LISTENERS_FIELD = ProxyServer.getInstance().getClass().getDeclaredField("listeners");
} catch (final ReflectiveOperationException ex) { LISTENERS_FIELD.setAccessible(true);
throw new IllegalStateException("Cannot create a modifier accessor", ex); } catch (ReflectiveOperationException e) {
throw new RuntimeException("Unable to access listeners field.", e);
} }
} }
@Override @Override
public void inject() throws Exception { @SuppressWarnings("unchecked")
try { public void inject() throws ReflectiveOperationException {
Class<?> pipelineUtils = Class.forName("net.md_5.bungee.netty.PipelineUtils"); Set<Channel> listeners = (Set<Channel>) LISTENERS_FIELD.get(ProxyServer.getInstance());
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");
field.setAccessible(true);
BungeeChannelInitializer newInit = new BungeeChannelInitializer((ChannelInitializer<Channel>) field.get(null)); // Inject the list
Set<Channel> wrapper = new SetWrapper<>(listeners, channel -> {
try {
injectChannel(channel);
} catch (Exception e) {
throw new RuntimeException(e);
}
});
this.forcefulFieldModifier.setField(field, null, newInit); LISTENERS_FIELD.set(ProxyServer.getInstance(), wrapper);
} catch (Exception e) {
Via.getPlatform().getLogger().severe("Unable to inject ViaVersion, please post these details on our GitHub and ensure you're using a compatible server version."); // Iterate through current list
throw e; for (Channel channel : listeners) {
injectChannel(channel);
} }
} }
@ -65,6 +77,40 @@ public class BungeeViaInjector implements ViaInjector {
Via.getPlatform().getLogger().severe("ViaVersion cannot remove itself from Bungee without a reboot!"); Via.getPlatform().getLogger().severe("ViaVersion cannot remove itself from Bungee without a reboot!");
} }
@SuppressWarnings("unchecked")
private void injectChannel(Channel channel) throws ReflectiveOperationException {
List<String> names = channel.pipeline().names();
ChannelHandler bootstrapAcceptor = null;
for (String name : names) {
ChannelHandler handler = channel.pipeline().get(name);
try {
ReflectionUtil.get(handler, "childHandler", ChannelInitializer.class);
bootstrapAcceptor = handler;
} catch (Exception e) {
// Not this one
}
}
// Default to first
if (bootstrapAcceptor == null) {
bootstrapAcceptor = channel.pipeline().first();
}
if (bootstrapAcceptor.getClass().getName().equals("net.md_5.bungee.query.QueryHandler")) {
return;
}
try {
ChannelInitializer<Channel> oldInit = ReflectionUtil.get(bootstrapAcceptor, "childHandler", ChannelInitializer.class);
ChannelInitializer<Channel> newInit = new BungeeChannelInitializer(oldInit);
ReflectionUtil.set(bootstrapAcceptor, "childHandler", newInit);
this.injectedChannels.add(channel);
} catch (NoSuchFieldException e) {
throw new RuntimeException("Unable to find core component 'childHandler', please check your plugins. issue: " + bootstrapAcceptor.getClass().getName());
}
}
@Override @Override
public int getServerProtocolVersion() throws Exception { public int getServerProtocolVersion() throws Exception {
@ -76,29 +122,64 @@ public class BungeeViaInjector implements ViaInjector {
return new IntLinkedOpenHashSet(getBungeeSupportedVersions()); return new IntLinkedOpenHashSet(getBungeeSupportedVersions());
} }
@SuppressWarnings("unchecked")
private List<Integer> getBungeeSupportedVersions() throws Exception { private List<Integer> getBungeeSupportedVersions() throws Exception {
return ReflectionUtil.getStatic(Class.forName("net.md_5.bungee.protocol.ProtocolConstants"), "SUPPORTED_VERSION_IDS", List.class); return ReflectionUtil.getStatic(Class.forName("net.md_5.bungee.protocol.ProtocolConstants"), "SUPPORTED_VERSION_IDS", List.class);
} }
private ChannelInitializer<Channel> getChannelInitializer() throws Exception {
Class<?> pipelineUtils = Class.forName("net.md_5.bungee.netty.PipelineUtils");
Field field = pipelineUtils.getDeclaredField("SERVER_CHILD");
field.setAccessible(true);
return (ChannelInitializer<Channel>) field.get(null);
}
@Override @Override
public JsonObject getDump() { public JsonObject getDump() {
JsonObject data = new JsonObject(); JsonObject data = new JsonObject();
try {
ChannelInitializer<Channel> initializer = getChannelInitializer(); // Generate information about current injections
data.addProperty("currentInitializer", initializer.getClass().getName()); JsonArray injectedChannelInitializers = new JsonArray();
if (initializer instanceof BungeeChannelInitializer) { for (Channel channel : this.injectedChannels) {
data.addProperty("originalInitializer", ((BungeeChannelInitializer) initializer).getOriginal().getClass().getName()); JsonObject channelInfo = new JsonObject();
channelInfo.addProperty("channelClass", channel.getClass().getName());
// Get information about the pipes for this channel
JsonArray pipeline = new JsonArray();
for (String pipeName : channel.pipeline().names()) {
JsonObject handlerInfo = new JsonObject();
handlerInfo.addProperty("name", pipeName);
ChannelHandler channelHandler = channel.pipeline().get(pipeName);
if (channelHandler == null) {
handlerInfo.addProperty("status", "INVALID");
continue;
}
handlerInfo.addProperty("class", channelHandler.getClass().getName());
try {
Object child = ReflectionUtil.get(channelHandler, "childHandler", ChannelInitializer.class);
handlerInfo.addProperty("childClass", child.getClass().getName());
if (child instanceof BungeeChannelInitializer) {
handlerInfo.addProperty("oldInit", ((BungeeChannelInitializer) child).getOriginal().getClass().getName());
}
} catch (ReflectiveOperationException e) {
// Don't display
}
pipeline.add(handlerInfo);
} }
} catch (Exception e) { channelInfo.add("pipeline", pipeline);
// Ignored, not printed in the dump
injectedChannelInitializers.add(channelInfo);
} }
data.add("injectedChannelInitializers", injectedChannelInitializers);
try {
Object list = LISTENERS_FIELD.get(ProxyServer.getInstance());
data.addProperty("currentList", list.getClass().getName());
if (list instanceof SetWrapper) {
data.addProperty("wrappedList", ((SetWrapper<?>) list).originalSet().getClass().getName());
}
} catch (ReflectiveOperationException ignored) {
// Ignored
}
return data; return data;
} }
} }

View File

@ -0,0 +1,44 @@
package com.viaversion.viaversion.util;
import com.google.common.collect.ForwardingSet;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Collection;
import java.util.Set;
import java.util.function.Consumer;
public class SetWrapper<E> extends ForwardingSet<E> {
private final Set<E> set;
private final Consumer<E> addListener;
public SetWrapper(Set<E> set, Consumer<E> addListener) {
this.set = set;
this.addListener = addListener;
}
@Override
public boolean add(@NonNull E element) {
addListener.accept(element);
return super.add(element);
}
@Override
public boolean addAll(Collection<? extends E> collection) {
for (E element : collection) {
addListener.accept(element);
}
return super.addAll(collection);
}
@Override
protected Set<E> delegate() {
return originalSet();
}
public Set<E> originalSet() {
return this.set;
}
}

View File

@ -22,10 +22,14 @@ import com.viaversion.viaversion.compatibility.ForcefulFieldModifier;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.Objects; import java.util.Objects;
/**
* @deprecated usage of sun.misc.Unsafe is discouraged and can stop working in future Java releases.
*/
@SuppressWarnings({ @SuppressWarnings({
"java:S1191", // SonarLint/-Qube/-Cloud: We need Unsafe for the modifier implementation. "java:S1191", // SonarLint/-Qube/-Cloud: We need Unsafe for the modifier implementation.
"java:S3011", // ^: We need to circumvent the access restrictions of fields. "java:S3011", // ^: We need to circumvent the access restrictions of fields.
}) })
@Deprecated
public final class UnsafeBackedForcefulFieldModifier implements ForcefulFieldModifier { public final class UnsafeBackedForcefulFieldModifier implements ForcefulFieldModifier {
private final sun.misc.Unsafe unsafe; private final sun.misc.Unsafe unsafe;