Skip to content

Commit

Permalink
Fix mixins
Browse files Browse the repository at this point in the history
  • Loading branch information
AlexProgrammerDE committed Nov 26, 2023
1 parent ad01a06 commit 0a4e1ae
Show file tree
Hide file tree
Showing 8 changed files with 264 additions and 10 deletions.
4 changes: 2 additions & 2 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ allprojects {

application {
applicationName = "ServerWrecker"
mainClass.set("net.pistonmaster.serverwrecker.ServerWreckerBootstrap")
mainClass.set("net.pistonmaster.serverwrecker.ServerWreckerLauncher")
}

tasks {
Expand Down Expand Up @@ -206,7 +206,7 @@ tasks.withType<Checkstyle> {

tasks.named<Jar>("jar") {
manifest {
attributes["Main-Class"] = "net.pistonmaster.serverwrecker.ServerWreckerBootstrap"
attributes["Main-Class"] = "net.pistonmaster.serverwrecker.ServerWreckerLauncher"
}
}

Expand Down
7 changes: 6 additions & 1 deletion buildSrc/src/main/kotlin/sw.java-conventions.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,12 @@ plugins {
id("io.freefair.lombok")
}

java.javaTarget(17)
java {
javaTarget(17)
toolchain {
languageVersion.set(JavaLanguageVersion.of(17))
}
}

tasks {
javadoc {
Expand Down
1 change: 1 addition & 0 deletions settings.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pluginManagement {

plugins {
id("com.gradle.enterprise") version "3.15.1"
id("org.gradle.toolchains.foojay-resolver-convention") version "0.7.0"
}

@Suppress("UnstableApiUsage")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@

import io.netty.util.ResourceLeakDetector;
import net.lenni0451.classtransform.TransformerManager;
import net.lenni0451.classtransform.additionalclassprovider.GuavaClassPathProvider;
import net.lenni0451.classtransform.mixinstranslator.MixinsTranslator;
import net.lenni0451.reflect.Agents;
import net.pistonmaster.serverwrecker.api.MixinExtension;
import net.pistonmaster.serverwrecker.builddata.BuildData;
import net.pistonmaster.serverwrecker.settings.DevSettings;
import net.pistonmaster.serverwrecker.settings.lib.SettingsHolder;
import net.pistonmaster.serverwrecker.util.CustomClassProvider;
import net.pistonmaster.serverwrecker.util.SWContextClassLoader;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.core.config.Configurator;
import org.fusesource.jansi.AnsiConsole;
Expand All @@ -41,7 +42,10 @@
import java.net.ServerSocket;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;

/**
* This class prepares the earliest work possible, such as loading mixins and
Expand Down Expand Up @@ -72,13 +76,14 @@ public class ServerWreckerBootstrap {
private ServerWreckerBootstrap() {
}

public static void main(String[] args) {
@SuppressWarnings("unused")
public static void bootstrap(String[] args, List<ClassLoader> classLoaders) {
injectAnsi();
setupLogging(SettingsHolder.EMPTY);

injectExceptionHandler();

initPlugins();
initPlugins(classLoaders);

// We may split client and server mixins in the future
var runServer = GraphicsEnvironment.isHeadless() || args.length > 0;
Expand All @@ -98,7 +103,12 @@ private static void injectMixinsAndRun(boolean runServer, String[] args) {
}
});

var classProvider = new GuavaClassPathProvider();
var classLoaders = new ArrayList<ClassLoader>();
classLoaders.add(ServerWreckerBootstrap.class.getClassLoader());
PLUGIN_MANAGER.getPlugins().forEach(pluginWrapper ->
classLoaders.add(pluginWrapper.getPluginClassLoader()));

var classProvider = new CustomClassProvider(classLoaders);
var transformerManager = new TransformerManager(classProvider);
transformerManager.addTransformerPreprocessor(new MixinsTranslator());
mixinPaths.forEach(transformerManager::addTransformer);
Expand Down Expand Up @@ -136,7 +146,7 @@ public static void injectExceptionHandler() {
});
}

private static void initPlugins() {
private static void initPlugins(List<ClassLoader> classLoaders) {
try {
Files.createDirectories(PLUGINS_FOLDER);
} catch (IOException e) {
Expand All @@ -149,6 +159,10 @@ private static void initPlugins() {
// Load all plugins available
PLUGIN_MANAGER.loadPlugins();
PLUGIN_MANAGER.startPlugins();

for (var plugin : PLUGIN_MANAGER.getPlugins()) {
classLoaders.add(plugin.getPluginClassLoader());
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* ServerWrecker
*
* Copyright (C) 2023 ServerWrecker
*
* 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/gpl-3.0.html>.
*/
package net.pistonmaster.serverwrecker;

import net.pistonmaster.serverwrecker.util.SWContextClassLoader;

import java.util.List;

/**
* This class only changes the classLoader for the rest of the program.
* This is so we can merge plugin and server classes.
*/
public class ServerWreckerLauncher {
private static final SWContextClassLoader SW_CONTEXT_CLASS_LOADER = new SWContextClassLoader();

public static void main(String[] args) {
Thread.currentThread().setContextClassLoader(SW_CONTEXT_CLASS_LOADER);

try {
SW_CONTEXT_CLASS_LOADER.loadClass("net.pistonmaster.serverwrecker.ServerWreckerBootstrap")
.getDeclaredMethod("bootstrap", String[].class, List.class)
.invoke(null, args, SW_CONTEXT_CLASS_LOADER.getCheckedClassLoaders());
} catch (ReflectiveOperationException e) {
throw new RuntimeException(e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,11 @@

/**
* This interface is used to load mixins from third-party plugins.
* Mixin paths are wildcard paths, so you can use something like
* "net.pistonmaster.serverwrecker.mixins.*" to load all mixins from a package.
* Use the direct class name for a single transformer <i>(e.g. <b>package.Transformer</b>)</i><br>
* Use the package ending with '*'
* for all transformers in the packet (not sub packages) <i>(e.g. <b>package.*</b>)</i><br>
* Use the package ending with '**'
* for all transformers in the package and sub packages <i>(e.g. <b>package.**</b>)</i><br>
*/
public interface MixinExtension extends ExtensionPoint {
/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* ServerWrecker
*
* Copyright (C) 2023 ServerWrecker
*
* 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/gpl-3.0.html>.
*/
package net.pistonmaster.serverwrecker.util;

import com.google.common.reflect.ClassPath;
import net.lenni0451.classtransform.utils.tree.IClassProvider;
import org.jetbrains.annotations.NotNull;

import javax.annotation.Nonnull;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Supplier;

import static net.lenni0451.classtransform.utils.ASMUtils.slash;
import static net.lenni0451.classtransform.utils.Sneaky.sneakySupply;

public class CustomClassProvider implements IClassProvider {
private final ClassPath[] classPaths;
private final ClassLoader[] classLoaders;

public CustomClassProvider(List<ClassLoader> classLoaders) {
this.classLoaders = classLoaders.toArray(new ClassLoader[0]);

try {
this.classPaths = new ClassPath[classLoaders.size()];
for (int i = 0; i < classLoaders.size(); i++) {
this.classPaths[i] = ClassPath.from(classLoaders.get(i));
}
} catch (Throwable t) {
throw new RuntimeException("Failed to initialize ClassPath", t);
}
}

@Override
public byte @NotNull [] getClass(@NotNull String name) throws ClassNotFoundException {
for (ClassLoader classLoader : this.classLoaders) {
byte[] bytes = this.getClassFromLoader(classLoader, name);
if (bytes != null) return bytes;
}

throw new ClassNotFoundException("Class not found: " + name);
}

private byte[] getClassFromLoader(ClassLoader classLoader, String name) {
try (InputStream is = classLoader.getResourceAsStream(slash(name) + ".class")) {
Objects.requireNonNull(is, "Class input stream is null");
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) > 0) baos.write(buf, 0, len);
return baos.toByteArray();
} catch (Throwable t) {
return null;
}
}

@Override
@Nonnull
public Map<String, Supplier<byte[]>> getAllClasses() {
Map<String, Supplier<byte[]>> map = new HashMap<>();
for (ClassPath classPath : this.classPaths) {
for (ClassPath.ClassInfo classInfo : classPath.getAllClasses()) {
map.put(classInfo.getName(), sneakySupply(() -> this.getClass(classInfo.getName())));
}
}
return map;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* ServerWrecker
*
* Copyright (C) 2023 ServerWrecker
*
* 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/gpl-3.0.html>.
*/
package net.pistonmaster.serverwrecker.util;

import lombok.Getter;
import net.lenni0451.reflect.Methods;
import net.lenni0451.reflect.exceptions.MethodInvocationException;
import net.pistonmaster.serverwrecker.ServerWreckerLauncher;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;

public class SWContextClassLoader extends ClassLoader {
@Getter
private final List<ClassLoader> checkedClassLoaders = new ArrayList<>();
private final Method findLoadedClassMethod = Objects.requireNonNull(Methods.getDeclaredMethod(ClassLoader.class, "loadClass", String.class, boolean.class));
private final ClassLoader platformClassLoader = ClassLoader.getSystemClassLoader().getParent();

public SWContextClassLoader() {
this(ClassLoader.getSystemClassLoader());
}

private SWContextClassLoader(ClassLoader parent) {
super(parent);
this.checkedClassLoaders.add(parent);
}

@Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
try {
return Methods.invoke(platformClassLoader, findLoadedClassMethod, name, resolve);
} catch (MethodInvocationException ignored) {
}

byte[] classData = loadClassData(name);
if (classData == null) {
throw new ClassNotFoundException();
}

if (name.equals(ServerWreckerLauncher.class.getName())) {
return super.loadClass(name, resolve);
}

c = defineClass(name, classData, 0, classData.length);
}

if (resolve) {
resolveClass(c);
}

return c;
}
}

private byte[] loadClassData(String className) {
var classPath = className.replace('.', '/') + ".class";

for (var classLoader : checkedClassLoaders) {
try (InputStream inputStream = classLoader.getResourceAsStream(classPath)) {
if (inputStream == null) {
continue;
}

return inputStream.readAllBytes();
} catch (IOException e) {
e.printStackTrace();
}
}

return null;
}
}

0 comments on commit 0a4e1ae

Please sign in to comment.