Commits vergleichen

...

1 Commits

Autor SHA1 Nachricht Datum
Lixfel
1f677a116d Classloader experiments
Einige Prüfungen sind fehlgeschlagen
SteamWarCI Build failed
2024-08-05 19:46:40 +02:00
5 geänderte Dateien mit 533 neuen und 4 gelöschten Zeilen

Datei anzeigen

@ -26,6 +26,7 @@ import de.steamwar.core.events.*;
import de.steamwar.message.Message;
import de.steamwar.network.NetworkReceiver;
import de.steamwar.network.handlers.ServerDataHandler;
import de.steamwar.plugin.SteamWarClassLoader;
import de.steamwar.sql.SchematicNode;
import de.steamwar.sql.SteamwarUser;
import de.steamwar.sql.internal.Statement;
@ -44,10 +45,9 @@ public class Core extends JavaPlugin{
public static final Message MESSAGE = new Message("SpigotCore", Core.class.getClassLoader());
private static final int VERSION = Integer.parseInt(Bukkit.getServer().getClass().getPackage().getName().split("_", 3)[1]);
@Deprecated
public static int getVersion(){
return VERSION;
return SteamWarClassLoader.MINECAFT_MAJOR_VERSION;
}
private static JavaPlugin instance;
@ -68,6 +68,13 @@ public class Core extends JavaPlugin{
@Override
public void onEnable() {
try {
System.out.println(new SteamWarClassLoader(this.getClassLoader()).loadClass("org.bukkit.craftbukkit.CraftServer").getName());
new SteamWarClassLoader(this.getClassLoader()).loadClass("de.steamwar.SteamWarPlugin");
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
errorHandler = new ErrorHandler();
crashDetector = new CrashDetector();

Datei anzeigen

@ -0,0 +1,198 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.plugin;
import de.steamwar.core.Core;
import jdk.internal.misc.Unsafe;
import lombok.AllArgsConstructor;
import lombok.experimental.UtilityClass;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
@UtilityClass
public final class Reflection {
@AllArgsConstructor
public static class Constructor {
private final java.lang.reflect.Constructor<?> constructor;
public Object newInstance(Object... arguments) {
try {
return constructor.newInstance(arguments);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot invoke constructor " + constructor, e);
}
}
}
@AllArgsConstructor
public static class Method {
private final java.lang.reflect.Method method;
public Object invoke(Object target, Object... arguments) {
try {
return method.invoke(target, arguments);
} catch (Exception e) {
throw new IllegalArgumentException("Cannot invoke method " + method, e);
}
}
public Object invokeStatic(Object... arguments) {
return invoke(null, arguments);
}
}
@AllArgsConstructor
public static class FieldAccessor<T> {
private final Field field;
@SuppressWarnings("unchecked")
public T get(Object target) {
try {
return (T) field.get(target);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot access reflection.", e);
}
}
public void set(Object target, Object value) {
try {
field.set(target, value);
} catch (IllegalAccessException e) {
throw new IllegalArgumentException("Cannot access reflection.", e);
}
}
}
public static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType) {
return getField(target, name, fieldType, 0);
}
public static <T> FieldAccessor<T> getField(String className, String name, Class<T> fieldType) {
return getField(getClass(className), name, fieldType, 0);
}
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index) {
return getField(target, null, fieldType, index);
}
public static <T> FieldAccessor<T> getField(String className, Class<T> fieldType, int index) {
return getField(getClass(className), fieldType, index);
}
public static <T> FieldAccessor<T> getField(Class<?> target, Class<T> fieldType, int index, Class<?>... parameters) {
return getField(target, null, fieldType, index, parameters);
}
private static <T> FieldAccessor<T> getField(Class<?> target, String name, Class<T> fieldType, int index, Class<?>... parameters) {
for (final Field field : target.getDeclaredFields()) {
if(matching(field, name, fieldType, parameters) && index-- <= 0) {
field.setAccessible(true);
return new FieldAccessor<T>(field);
}
}
// Search in parent classes
if (target.getSuperclass() != null)
return getField(target.getSuperclass(), name, fieldType, index);
throw new IllegalArgumentException("Cannot find field with type " + fieldType);
}
private static <T> boolean matching(Field field, String name, Class<T> fieldType, Class<?>... parameters) {
if(name != null && !field.getName().equals(name))
return false;
if(!fieldType.isAssignableFrom(field.getType()))
return false;
if(parameters.length > 0) {
Type[] arguments = ((ParameterizedType)field.getGenericType()).getActualTypeArguments();
for(int i = 0; i < parameters.length; i++) {
if(arguments[i] instanceof ParameterizedType ? ((ParameterizedType) arguments[i]).getRawType() != parameters[i] : arguments[i] != parameters[i])
return false;
}
}
return true;
}
public static Method getMethod(String className, String methodName, Class<?>... params) {
return getTypedMethod(getClass(className), methodName, null, params);
}
public static Method getMethod(Class<?> clazz, String methodName, Class<?>... params) {
return getTypedMethod(clazz, methodName, null, params);
}
public static Method getTypedMethod(Class<?> clazz, Class<?> returnType, Class<?>... params) {
return getTypedMethod(clazz, null, returnType, params);
}
public static Method getTypedMethod(Class<?> clazz, String methodName, Class<?> returnType, Class<?>... params) {
for (final java.lang.reflect.Method method : clazz.getDeclaredMethods()) {
if ((methodName == null || method.getName().equals(methodName))
&& (returnType == null || method.getReturnType().equals(returnType))
&& Arrays.equals(method.getParameterTypes(), params)) {
method.setAccessible(true);
return new Method(method);
}
}
// Search in every superclass
if (clazz.getSuperclass() != null)
return getTypedMethod(clazz.getSuperclass(), methodName, returnType, params);
throw new IllegalStateException(String.format("Unable to find method %s (%s).", methodName, Arrays.asList(params)));
}
public static Constructor getConstructor(String className, Class<?>... params) {
return getConstructor(getClass(className), params);
}
public static Constructor getConstructor(Class<?> clazz, Class<?>... params) {
for (final java.lang.reflect.Constructor<?> constructor : clazz.getDeclaredConstructors()) {
if (Arrays.equals(constructor.getParameterTypes(), params)) {
constructor.setAccessible(true);
return new Constructor(constructor);
}
}
throw new IllegalStateException(String.format("Unable to find constructor for %s (%s).", clazz, Arrays.asList(params)));
}
public static Class<?> getClass(String name) {
try {
return Class.forName(name);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException("Cannot find " + name, e);
}
}
public static Object newInstance(Class<?> clazz) {
try {
if (Core.getVersion() > 15) {
return Unsafe.getUnsafe().allocateInstance(clazz);
} else {
return clazz.newInstance();
}
} catch (InstantiationException | IllegalAccessException e) {
throw new SecurityException("Could not create object", e);
}
}
}

Datei anzeigen

@ -0,0 +1,139 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.plugin;
import com.comphenix.tinyprotocol.Reflection;
import de.steamwar.core.CommandRemover;
import lombok.AllArgsConstructor;
import lombok.Getter;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.plugin.PluginLogger;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageListener;
import org.bukkit.scheduler.BukkitScheduler;
import org.bukkit.scheduler.BukkitTask;
import java.io.File;
import java.util.*;
@Getter
@AllArgsConstructor
public class SWPlugin {
private final JavaPlugin bukkit;
private final PluginLogger logger;
private final File dataFolder;
private final Map<Runnable, BukkitTask> tasks = new HashMap<>();
private final List<String> commands = new ArrayList<>();
private final Set<Listener> listeners = new HashSet<>();
private final List<String> outgoingChannels = new ArrayList<>();
private final List<String> incomingChannels = new ArrayList<>();
//TODO commands
public void load() {
}
public void unload() {
tasks.values().forEach(BukkitTask::cancel);
tasks.clear();
outgoingChannels.forEach(channel -> bukkit.getServer().getMessenger().unregisterOutgoingPluginChannel(bukkit, channel));
outgoingChannels.clear();
incomingChannels.forEach(channel -> bukkit.getServer().getMessenger().unregisterIncomingPluginChannel(bukkit, channel));
incomingChannels.clear();
listeners.forEach(HandlerList::unregisterAll);
listeners.clear();
commands.forEach(CommandRemover::removeAll);
commands.clear();
}
public YamlConfiguration getConfig() {
return YamlConfiguration.loadConfiguration(new File(dataFolder, "config.yml"));
}
public void runTask(Runnable runnable) {
getScheduler().runTask(bukkit, runnable);
}
public Runnable runTaskLater(Runnable runnable, long delay) {
return makeCancellable(runnable, getScheduler().runTaskLater(bukkit, runnable, delay));
}
public Runnable runTaskTimer(Runnable runnable, long delay, long frequency) {
return makeCancellable(runnable, getScheduler().runTaskTimer(bukkit, runnable, delay, frequency));
}
public void registerOutgoingPluginChannel(String channel) {
outgoingChannels.add(channel);
bukkit.getServer().getMessenger().registerOutgoingPluginChannel(bukkit, channel);
}
public void registerIncomingPluginChannel(String channel, PluginMessageListener listener) {
incomingChannels.add(channel);
bukkit.getServer().getMessenger().registerIncomingPluginChannel(bukkit, channel, listener);
}
public void registerEvents(Listener listener) {
listeners.add(listener);
bukkit.getServer().getPluginManager().registerEvents(listener, bukkit);
}
public void unregisterEvents(Listener listener) {
HandlerList.unregisterAll(listener);
listeners.remove(listener);
}
public void callEvent(Event event) {
bukkit.getServer().getPluginManager().callEvent(event);
}
private static final com.comphenix.tinyprotocol.Reflection.FieldAccessor<SimpleCommandMap> craftServerCommandMap = Reflection.getField("{obc}.CraftServer", "commandMap", SimpleCommandMap.class);
public void setExecutor(String name, CommandExecutor executor) {
//TODO tab completes
SimpleCommandMap commandMap = craftServerCommandMap.get(bukkit.getServer());
commandMap.register(bukkit.getName(), new Command(name) {
@Override
public boolean execute(CommandSender commandSender, String s, String[] strings) {
return executor.onCommand(commandSender, this, s, strings);
}
});
}
private BukkitScheduler getScheduler() {
return bukkit.getServer().getScheduler();
}
private Runnable makeCancellable(Runnable runnable, BukkitTask task) {
tasks.put(runnable, task);
return () -> {
task.cancel();
tasks.remove(runnable);
};
}
}

Datei anzeigen

@ -0,0 +1,98 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.plugin;
import org.bukkit.plugin.InvalidDescriptionException;
import org.bukkit.plugin.InvalidPluginException;
import org.bukkit.plugin.PluginLogger;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
public class SpigotCore extends JavaPlugin {
public static SpigotCore getInstance() {
return JavaPlugin.getPlugin(SpigotCore.class);
}
private static final String BUKKIT_INDICATOR = "bukkit:";
private static final String STEAMWAR_INDICATOR = "steamwar:";
private final SteamWarClassLoader loader;
private final List<SWPlugin> loaded = new ArrayList<>();
private final List<SWPlugin> enabled = new ArrayList<>();
public SpigotCore() {
loader = new SteamWarClassLoader(this.getClassLoader());
}
@Override
public void onLoad() {
//TODO more dynamic than file
File file = new File("plugins.properties");
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
while(true) {
String plugin = reader.readLine();
if(plugin == null)
break;
if(plugin.startsWith(BUKKIT_INDICATOR))
loadBukkit(plugin.substring(BUKKIT_INDICATOR.length()));
else if(plugin.startsWith(STEAMWAR_INDICATOR))
loadSteamWar(plugin.substring(STEAMWAR_INDICATOR.length()));
}
} catch (FileNotFoundException e) {
getLogger().log(Level.WARNING, "Did not load any plugins due to missing plugins.properties file.");
} catch (IOException e) {
getLogger().log(Level.WARNING, "Unhandled error reading plugins.properties file.", e);
}
}
@Override
public void onEnable() {
}
@Override
public void onDisable() {
}
private void loadSteamWar(String path) {
loader.loadPlugin(new File(path));
loaded.add(new SWPlugin(this, plugin, new PluginLogger(todo), ));
//TODO multiple plugins per file
}
private void loadBukkit(String path) {
try {
//Default plugin config folder is generated from parent file folder
getServer().getPluginManager().loadPlugin(new File(path));
} catch (InvalidPluginException | InvalidDescriptionException e) {
getLogger().log(Level.SEVERE, e, () -> "Unhandled error loading plugin: " + path);
}
}
}

Datei anzeigen

@ -0,0 +1,87 @@
/*
* This file is a part of the SteamWar software.
*
* Copyright (C) 2024 SteamWar.de-Serverteam
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package de.steamwar.plugin;
import org.bukkit.Bukkit;
import java.io.File;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
public class SteamWarClassLoader extends URLClassLoader {
static {
registerAsParallelCapable();
}
public SteamWarClassLoader(ClassLoader parent) {
super(new URL[0], parent);
/*new URL();
ZipFile zipFile = new ZipFile("string");
addURL();
getResources("").nextElement().openStream();*/
}
public void loadPlugin(File file, String name, SWPlugin context) {
//TODO SteamWarPlugin as Class with constructor taking Context (unregister methods for disable)
try {
addURL(file.toURI().toURL());
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
public static final int MINECAFT_MAJOR_VERSION;
public static final int MINECAFT_MINOR_VERSION;
static {
String[] version = Bukkit.getServer().getBukkitVersion().split("-")[0].split("\\.");
MINECAFT_MAJOR_VERSION = Integer.parseInt(version[1]);
MINECAFT_MINOR_VERSION = version.length > 2 ? Integer.parseInt(version[2]) : 0;
}
private static final String CRAFTBUKKIT_PREFIX = "org.bukkit.craftbukkit";
private static final String CRAFTBUKKIT_RELOCATED_PREFIX = Bukkit.getServer().getClass().getPackage().getName();
private static final boolean CRAFTBUKKIT_RELOCATED = !CRAFTBUKKIT_RELOCATED_PREFIX.substring(CRAFTBUKKIT_PREFIX.length()).isEmpty();
private static final boolean NET_MINECRAFT_RELOCATED = MINECAFT_MAJOR_VERSION < 17;
private static final String NET_MINECRAFT_RELOCATED_PREFIX = "net.minecraft.server" + CRAFTBUKKIT_RELOCATED_PREFIX.substring(CRAFTBUKKIT_PREFIX.length());
public static Class<?> getClass(String name) {
//TODO running on mojang mapped servers (mojang mappings only available starting with 1.14.4)
if(CRAFTBUKKIT_RELOCATED && name.startsWith(CRAFTBUKKIT_PREFIX)) {
return Class.forName(CRAFTBUKKIT_RELOCATED_PREFIX + name.substring(CRAFTBUKKIT_PREFIX.length()));
}else if(NET_MINECRAFT_RELOCATED && name.startsWith("net.minecraft")) {
return Class.forName(NET_MINECRAFT_RELOCATED_PREFIX + "." + name.substring(name.lastIndexOf('.')));
}
throw new ClassNotFoundException(name);
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//TODO running on mojang mapped servers
if(CRAFTBUKKIT_RELOCATED && name.startsWith(CRAFTBUKKIT_PREFIX)) {
return super.loadClass(CRAFTBUKKIT_RELOCATED_PREFIX + name.substring(CRAFTBUKKIT_PREFIX.length()));
}else if(NET_MINECRAFT_RELOCATED && name.startsWith("net.minecraft")) {
return super.loadClass(NET_MINECRAFT_RELOCATED_PREFIX + "." + name.substring(name.lastIndexOf('.')));
}
throw new ClassNotFoundException(name);
}
}