From 961f2d0a8af1a6d834089902fe2cc5c64fa8f56c Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Sun, 24 Oct 2021 15:42:08 +0800 Subject: [PATCH] Make it able to load as a mod --- README.md | 13 +- .../bukkit/ColouredConsoleSenderMixin.java | 9 ++ .../common/mixin/bukkit/CraftServerMixin.java | 7 +- .../mod/util/log/ArclightPluginLogger.java | 7 +- arclight-forge/build.gradle | 25 ++-- .../io/izzel/arclight/server/Launcher.java | 2 +- .../arclight/boot/AbstractBootstrap.java | 55 +++++++ .../arclight/boot/ArclightBootstrap.java | 100 ------------- .../io/izzel/arclight/boot/Main_Forge.java | 28 ---- .../application/ApplicationBootstrap.java | 47 ++++++ .../arclight/boot/application/Main_Forge.java | 44 ++++++ .../boot/asm/ArclightImplementer.java | 12 ++ .../arclight/boot/asm/LoggerTransformer.java | 38 +++++ .../boot/{ => mod}/ArclightLocator_Forge.java | 7 +- .../izzel/arclight/boot/mod/ModBootstrap.java | 134 ++++++++++++++++++ .../services/java.util.function.Consumer | 2 +- ...necraftforge.forgespi.locating.IModLocator | 2 +- .../forgeinstaller/ForgeInstaller.java | 46 ++++-- 18 files changed, 408 insertions(+), 170 deletions(-) create mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java delete mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightBootstrap.java delete mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/Main_Forge.java create mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java create mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java create mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java rename arclight-forge/src/main/java/io/izzel/arclight/boot/{ => mod}/ArclightLocator_Forge.java (93%) create mode 100644 arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java diff --git a/README.md b/README.md index d05a3b23..2fa6edc8 100644 --- a/README.md +++ b/README.md @@ -17,8 +17,12 @@ A Bukkit server implementation utilizing Mixin. ## Installing -1. Download the jar from [release page](https://github.com/IzzelAliz/Arclight/releases) or build server. (see the table above) -2. Launch with command `java -jar arclight-forge--.jar nogui`. The `nogui` argument will disable the server control panel. +* Download the jar from [release page](https://github.com/IzzelAliz/Arclight/releases) or build server. (see the table + above) +* There are 2 ways to start Arclight: + * (**Recommended**) Launch with command `java -jar arclight-forge--.jar nogui`. The `nogui` argument will disable the + server control panel. + * Drop the downloaded jar into `mods` folder and start a forge server. ## Support @@ -38,9 +42,8 @@ This project is licensed under [GPL v3](LICENSE). [![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com) -YourKit supports open source projects with innovative and intelligent tools -for monitoring and profiling Java and .NET applications. -YourKit is the creator of YourKit Java Profiler, +YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET +applications. YourKit is the creator of YourKit Java Profiler, YourKit .NET Profiler, and YourKit YouMonitor. diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/ColouredConsoleSenderMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/ColouredConsoleSenderMixin.java index e88e7597..7ec03894 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/ColouredConsoleSenderMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/ColouredConsoleSenderMixin.java @@ -1,16 +1,25 @@ package io.izzel.arclight.common.mixin.bukkit; +import jline.Terminal; +import jline.console.ConsoleReader; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.bukkit.craftbukkit.v.command.ColouredConsoleSender; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Overwrite; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; @Mixin(value = ColouredConsoleSender.class, remap = false) public class ColouredConsoleSenderMixin extends CraftConsoleCommandSenderMixin { private static final Logger LOGGER = LogManager.getLogger("Console"); + @Redirect(method = "", at = @At(value = "INVOKE", target = "Ljline/console/ConsoleReader;getTerminal()Ljline/Terminal;")) + private Terminal arclight$terminal(ConsoleReader instance) { + return null; + } + /** * @author IzzelAliz * @reason use TerminalConsoleAppender diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftServerMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftServerMixin.java index 216f54c1..83a12729 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftServerMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/CraftServerMixin.java @@ -37,7 +37,6 @@ import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; -import java.io.IOException; import java.util.Locale; import java.util.Map; import java.util.logging.Logger; @@ -83,11 +82,7 @@ public abstract class CraftServerMixin implements CraftServerBridge { */ @Overwrite(remap = false) public ConsoleReader getReader() { - try { - return new ConsoleReader(); - } catch (IOException e) { - return null; - } + return null; } @Inject(method = "unloadWorld(Lorg/bukkit/World;Z)Z", remap = false, require = 1, at = @At(value = "INVOKE", ordinal = 1, target = "Ljava/util/Map;remove(Ljava/lang/Object;)Ljava/lang/Object;")) diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/log/ArclightPluginLogger.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/log/ArclightPluginLogger.java index 18101c56..d4f5ad17 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/log/ArclightPluginLogger.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/log/ArclightPluginLogger.java @@ -1,20 +1,23 @@ package io.izzel.arclight.common.mod.util.log; +import org.apache.logging.log4j.jul.LogManager; import org.bukkit.plugin.Plugin; import org.bukkit.plugin.PluginLogger; -import java.util.logging.LogManager; import java.util.logging.LogRecord; import java.util.logging.Logger; public class ArclightPluginLogger extends PluginLogger { + private static final LogManager JUL_MANAGER = + java.util.logging.LogManager.getLogManager() instanceof LogManager instance ? instance : new LogManager(); + private final Logger logger; public ArclightPluginLogger(Plugin context) { super(context); String prefix = context.getDescription().getPrefix(); - logger = LogManager.getLogManager().getLogger(prefix == null ? context.getName() : prefix); + logger = JUL_MANAGER.getLogger(prefix == null ? context.getName() : prefix); } @Override diff --git a/arclight-forge/build.gradle b/arclight-forge/build.gradle index 6e3534ea..1875d077 100644 --- a/arclight-forge/build.gradle +++ b/arclight-forge/build.gradle @@ -25,7 +25,8 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(16) configurations { installer embed - implementation.extendsFrom(embed) + gson + implementation.extendsFrom(embed, gson) } repositories { @@ -40,12 +41,12 @@ repositories { maven { url = 'https://maven.izzel.io/releases' } } -def embedLibs = ["org.spongepowered:mixin:$mixinVersion", 'org.yaml:snakeyaml:1.28', +def embedLibs = [/*"org.spongepowered:mixin:$mixinVersion", */ 'org.yaml:snakeyaml:1.28', 'org.xerial:sqlite-jdbc:3.34.0', 'mysql:mysql-connector-java:5.1.49', /*'commons-lang:commons-lang:2.6',*/ 'com.googlecode.json-simple:json-simple:1.1.1', - 'org.apache.logging.log4j:log4j-jul:2.11.2', 'net.md-5:SpecialSource:1.10.0', + 'org.apache.logging.log4j:log4j-jul:2.14.1', 'net.md-5:SpecialSource:1.10.0', 'org.jline:jline-terminal-jansi:3.12.1', 'org.fusesource.jansi:jansi:1.18', - 'org.jline:jline-terminal:3.12.1', 'org.jline:jline-reader:3.12.1', + /*'org.jline:jline-terminal:3.12.1', 'org.jline:jline-reader:3.12.1',*/ 'jline:jline:2.12.1', 'org.apache.maven:maven-resolver-provider:3.8.1', 'org.apache.maven.resolver:maven-resolver-connector-basic:1.6.2', 'org.apache.maven.resolver:maven-resolver-transport-http:1.6.2', 'org.apache.maven:maven-model:3.8.1', 'org.codehaus.plexus:plexus-utils:3.2.1', @@ -54,8 +55,8 @@ def embedLibs = ["org.spongepowered:mixin:$mixinVersion", 'org.yaml:snakeyaml:1. 'org.apache.maven:maven-repository-metadata:3.8.1', 'org.apache.maven.resolver:maven-resolver-api:1.6.2', 'org.apache.maven.resolver:maven-resolver-spi:1.6.2', 'org.apache.maven.resolver:maven-resolver-util:1.6.2', 'org.apache.maven.resolver:maven-resolver-impl:1.6.2', 'org.apache.httpcomponents:httpclient:4.5.12', - 'org.apache.httpcomponents:httpcore:4.4.13', 'commons-codec:commons-codec:1.11', - 'org.slf4j:jcl-over-slf4j:1.7.30', 'org.apache.logging.log4j:log4j-slf4j18-impl:2.14.1', + 'org.apache.httpcomponents:httpcore:4.4.13',/* 'commons-codec:commons-codec:1.11',*/ + 'org.slf4j:jcl-over-slf4j:1.7.30', /*'org.apache.logging.log4j:log4j-slf4j18-impl:2.14.1',*/ 'org.spongepowered:configurate-hocon:3.6.1', 'org.spongepowered:configurate-core:3.6.1', 'com.typesafe:config:1.3.1'] @@ -66,11 +67,11 @@ dependencies { implementation 'cpw.mods:modlauncher:9.0.7' implementation 'cpw.mods:securejarhandler:0.9.45' implementation 'net.minecraftforge:forgespi:4.0.9' - implementation 'com.google.code.gson:gson:2.8.0' + gson 'com.google.code.gson:gson:2.8.8' implementation 'org.apache.logging.log4j:log4j-api:2.14.1' implementation 'org.jetbrains:annotations:19.0.0' implementation 'org.spongepowered:mixin:0.8.3' - implementation 'org.apache.logging.log4j:log4j-jul:2.11.2' + implementation 'org.apache.logging.log4j:log4j-jul:2.14.1' for (def lib : embedLibs) { installer lib } @@ -81,7 +82,9 @@ dependencies { embed(project(':i18n-config')) { transitive = false } - embed(project(':forge-installer')) + embed(project(':forge-installer')) { + transitive = false + } } jar { @@ -104,6 +107,10 @@ jar { it.from(project(':arclight-common').tasks.jar.outputs.files.collect()) it.rename { name -> 'common.jar' } } + into('/') { + it.from(configurations.gson.collect()) + it.rename { name -> 'gson.jar' } + } from sourceSets.applaunch.output.classesDirs duplicatesStrategy = DuplicatesStrategy.EXCLUDE dependsOn(project(':arclight-common').tasks.jar) diff --git a/arclight-forge/src/applaunch/java/io/izzel/arclight/server/Launcher.java b/arclight-forge/src/applaunch/java/io/izzel/arclight/server/Launcher.java index e7f8d190..23434e65 100644 --- a/arclight-forge/src/applaunch/java/io/izzel/arclight/server/Launcher.java +++ b/arclight-forge/src/applaunch/java/io/izzel/arclight/server/Launcher.java @@ -1,6 +1,6 @@ package io.izzel.arclight.server; -import io.izzel.arclight.boot.Main_Forge; +import io.izzel.arclight.boot.application.Main_Forge; public class Launcher { diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java new file mode 100644 index 00000000..bdd8352a --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/AbstractBootstrap.java @@ -0,0 +1,55 @@ +package io.izzel.arclight.boot; + +import com.google.gson.internal.bind.TypeAdapters; +import com.google.gson.reflect.TypeToken; +import io.izzel.arclight.api.ArclightVersion; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.i18n.ArclightLocale; +import org.apache.logging.log4j.LogManager; + +import java.io.InputStream; +import java.lang.reflect.Field; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.Attributes; +import java.util.jar.Manifest; +import java.util.stream.Collectors; + +public class AbstractBootstrap { + + protected void dirtyHacks() throws Exception { + TypeAdapters.ENUM_FACTORY.create(null, TypeToken.get(Object.class)); + Field field = TypeAdapters.class.getDeclaredField("ENUM_FACTORY"); + Object base = Unsafe.staticFieldBase(field); + long offset = Unsafe.staticFieldOffset(field); + Unsafe.putObjectVolatile(base, offset, new EnumTypeFactory()); + } + + protected void setupMod() throws Exception { + ArclightVersion.setVersion(ArclightVersion.v1_17_R1); + try (InputStream stream = getClass().getModule().getResourceAsStream("/META-INF/MANIFEST.MF")) { + Manifest manifest = new Manifest(stream); + Attributes attributes = manifest.getMainAttributes(); + String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); + extract(getClass().getModule().getResourceAsStream("/common.jar"), version); + String buildTime = attributes.getValue("Implementation-Timestamp"); + LogManager.getLogger("Arclight").info(ArclightLocale.getInstance().get("logo"), version, buildTime); + } + } + + private void extract(InputStream path, String version) throws Exception { + System.setProperty("arclight.version", version); + var dir = Paths.get(".arclight", "mod_file"); + if (!Files.exists(dir)) { + Files.createDirectories(dir); + } + var mod = dir.resolve(version + ".jar"); + if (!Files.exists(mod) || Boolean.getBoolean("arclight.alwaysExtract")) { + for (Path old : Files.list(dir).collect(Collectors.toList())) { + Files.delete(old); + } + Files.copy(path, mod); + } + } +} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightBootstrap.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightBootstrap.java deleted file mode 100644 index 785d55be..00000000 --- a/arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightBootstrap.java +++ /dev/null @@ -1,100 +0,0 @@ -package io.izzel.arclight.boot; - -import com.google.common.collect.ImmutableMap; -import com.google.gson.internal.bind.TypeAdapters; -import com.google.gson.reflect.TypeToken; -import io.izzel.arclight.api.ArclightVersion; -import io.izzel.arclight.api.EnumHelper; -import io.izzel.arclight.api.Unsafe; -import io.izzel.arclight.i18n.ArclightConfig; -import io.izzel.arclight.i18n.ArclightLocale; -import org.apache.logging.log4j.LogManager; - -import java.io.File; -import java.io.InputStream; -import java.lang.reflect.Field; -import java.net.URI; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.ServiceLoader; -import java.util.function.Consumer; -import java.util.jar.Attributes; -import java.util.jar.Manifest; -import java.util.stream.Collectors; - -public class ArclightBootstrap implements Consumer { - - private static final int MIN_DEPRECATED_VERSION = 60; - private static final int MIN_DEPRECATED_JAVA_VERSION = 16; - - @Override - @SuppressWarnings("unchecked") - public void accept(String[] args) { - System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); - System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.boot.log.ArclightLoggerAdapter"); - System.setProperty("log4j.configurationFile", "arclight-log4j2.xml"); - ArclightLocale.info("i18n.using-language", ArclightConfig.spec().getLocale().getCurrent(), ArclightConfig.spec().getLocale().getFallback()); - try { - int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version")); - if (javaVersion < MIN_DEPRECATED_VERSION) { - ArclightLocale.error("java.deprecated", System.getProperty("java.version"), MIN_DEPRECATED_JAVA_VERSION); - Thread.sleep(3000); - } - Unsafe.ensureClassInitialized(EnumHelper.class); - } catch (Throwable t) { - System.err.println("Your Java is not compatible with Arclight."); - t.printStackTrace(); - return; - } - try { - this.setupMod(); - this.dirtyHacks(); - ServiceLoader.load(getClass().getModule().getLayer(), Consumer.class).stream() - .filter(it -> !it.type().getName().contains("Arclight")) - .findFirst().orElseThrow().get().accept(args); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Fail to launch Arclight."); - } - } - - private void dirtyHacks() throws Exception { - TypeAdapters.ENUM_FACTORY.create(null, TypeToken.get(Object.class)); - Field field = TypeAdapters.class.getDeclaredField("ENUM_FACTORY"); - Object base = Unsafe.staticFieldBase(field); - long offset = Unsafe.staticFieldOffset(field); - Unsafe.putObjectVolatile(base, offset, new EnumTypeFactory()); - } - - private void setupMod() throws Exception { - ArclightVersion.setVersion(ArclightVersion.v1_17_R1); - URI uri = new File(System.getProperty("arclight.selfPath")).toURI(); - FileSystem fs = FileSystems.newFileSystem(URI.create("jar:" + uri), ImmutableMap.of("create", "true")); - try (InputStream stream = Files.newInputStream(fs.getPath("/META-INF/MANIFEST.MF"))) { - Manifest manifest = new Manifest(stream); - Attributes attributes = manifest.getMainAttributes(); - String version = attributes.getValue(Attributes.Name.IMPLEMENTATION_VERSION); - extract(fs.getPath("/common.jar"), version); - String buildTime = attributes.getValue("Implementation-Timestamp"); - LogManager.getLogger("Arclight").info(ArclightLocale.getInstance().get("logo"), version, buildTime); - } - } - - private void extract(Path path, String version) throws Exception { - System.setProperty("arclight.version", version); - var dir = Paths.get(".arclight", "mod_file"); - if (!Files.exists(dir)) { - Files.createDirectories(dir); - } - var mod = dir.resolve(version + ".jar"); - if (!Files.exists(mod) || Boolean.getBoolean("arclight.alwaysExtract")) { - for (Path old : Files.list(dir).collect(Collectors.toList())) { - Files.delete(old); - } - Files.copy(path, mod); - } - } -} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/Main_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/Main_Forge.java deleted file mode 100644 index 8c8d7632..00000000 --- a/arclight-forge/src/main/java/io/izzel/arclight/boot/Main_Forge.java +++ /dev/null @@ -1,28 +0,0 @@ -package io.izzel.arclight.boot; - -import io.izzel.arclight.forgeinstaller.ForgeInstaller; - -import java.io.File; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.stream.Stream; - -public class Main_Forge { - - public static void main(String[] args) throws Throwable { - try { - Map.Entry> install = ForgeInstaller.install(); - var path = new File(Main_Forge.class.getProtectionDomain().getCodeSource().getLocation().toURI()).getCanonicalPath(); - System.setProperty("arclight.selfPath", path); - var cl = Class.forName(install.getKey()); - var method = cl.getMethod("main", String[].class); - var target = Stream.concat(install.getValue().stream(), Arrays.stream(args)).toArray(String[]::new); - method.invoke(null, (Object) target); - } catch (Exception e) { - e.printStackTrace(); - System.err.println("Fail to launch Arclight."); - System.exit(-1); - } - } -} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java new file mode 100644 index 00000000..45e19751 --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/application/ApplicationBootstrap.java @@ -0,0 +1,47 @@ +package io.izzel.arclight.boot.application; + +import io.izzel.arclight.api.EnumHelper; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.boot.AbstractBootstrap; +import io.izzel.arclight.i18n.ArclightConfig; +import io.izzel.arclight.i18n.ArclightLocale; + +import java.util.ServiceLoader; +import java.util.function.Consumer; + +public class ApplicationBootstrap extends AbstractBootstrap implements Consumer { + + private static final int MIN_DEPRECATED_VERSION = 60; + private static final int MIN_DEPRECATED_JAVA_VERSION = 16; + + @Override + @SuppressWarnings("unchecked") + public void accept(String[] args) { + System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager"); + System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.boot.log.ArclightLoggerAdapter"); + System.setProperty("log4j.configurationFile", "arclight-log4j2.xml"); + ArclightLocale.info("i18n.using-language", ArclightConfig.spec().getLocale().getCurrent(), ArclightConfig.spec().getLocale().getFallback()); + try { + int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version")); + if (javaVersion < MIN_DEPRECATED_VERSION) { + ArclightLocale.error("java.deprecated", System.getProperty("java.version"), MIN_DEPRECATED_JAVA_VERSION); + Thread.sleep(3000); + } + Unsafe.ensureClassInitialized(EnumHelper.class); + } catch (Throwable t) { + System.err.println("Your Java is not compatible with Arclight."); + t.printStackTrace(); + return; + } + try { + this.setupMod(); + this.dirtyHacks(); + ServiceLoader.load(getClass().getModule().getLayer(), Consumer.class).stream() + .filter(it -> !it.type().getName().contains("arclight")) + .findFirst().orElseThrow().get().accept(args); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Fail to launch Arclight."); + } + } +} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java new file mode 100644 index 00000000..2fe46336 --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/application/Main_Forge.java @@ -0,0 +1,44 @@ +package io.izzel.arclight.boot.application; + +import java.lang.invoke.MethodHandles; +import java.lang.invoke.MethodType; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.stream.Stream; + +public class Main_Forge { + + public static void main(String[] args) throws Throwable { + try { + Map.Entry> install = forgeInstall(); + var cl = Class.forName(install.getKey()); + var method = cl.getMethod("main", String[].class); + var target = Stream.concat(install.getValue().stream(), Arrays.stream(args)).toArray(String[]::new); + method.invoke(null, (Object) target); + } catch (Exception e) { + e.printStackTrace(); + System.err.println("Fail to launch Arclight."); + System.exit(-1); + } + } + + @SuppressWarnings("unchecked") + private static Map.Entry> forgeInstall() throws Throwable { + var path = Paths.get(".arclight", "gson.jar"); + if (!Files.exists(path)) { + Files.createDirectories(path.getParent()); + Files.copy(Objects.requireNonNull(Main_Forge.class.getResourceAsStream("/gson.jar")), path); + } + try (var loader = new URLClassLoader(new URL[]{path.toUri().toURL(), Main_Forge.class.getProtectionDomain().getCodeSource().getLocation()}, ClassLoader.getPlatformClassLoader())) { + var cl = loader.loadClass("io.izzel.arclight.forgeinstaller.ForgeInstaller"); + var handle = MethodHandles.lookup().findStatic(cl, "applicationInstall", MethodType.methodType(Map.Entry.class)); + return (Map.Entry>) handle.invoke(); + } + } +} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java index 46604d8f..84146c0c 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/ArclightImplementer.java @@ -29,6 +29,15 @@ public class ArclightImplementer implements ILaunchPluginService { private final Map implementers = new HashMap<>(); private volatile Consumer auditAcceptor; private ITransformerLoader transformerLoader; + private final boolean logger; + + public ArclightImplementer() { + this(false); + } + + public ArclightImplementer(boolean logger) { + this.logger = logger; + } @Override public String name() { @@ -42,6 +51,9 @@ public class ArclightImplementer implements ILaunchPluginService { this.implementers.put("switch", SwitchTableFixer.INSTANCE); this.implementers.put("async", AsyncCatcher.INSTANCE); this.implementers.put("entitytype", EntityTypePatcher.INSTANCE); + if (this.logger) { + this.implementers.put("logger", new LoggerTransformer()); + } } @Override diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java new file mode 100644 index 00000000..e7387ca4 --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/asm/LoggerTransformer.java @@ -0,0 +1,38 @@ +package io.izzel.arclight.boot.asm; + +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import org.apache.logging.log4j.jul.LogManager; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; + +import java.util.logging.Logger; + +public class LoggerTransformer implements Implementer { + + private static final LogManager JUL_MANAGER = new LogManager(); + + @Override + public boolean processClass(ClassNode node, ILaunchPluginService.ITransformerLoader transformerLoader) { + var transform = false; + for (var mn : node.methods) { + for (var insn : mn.instructions) { + if (insn.getOpcode() == Opcodes.INVOKESTATIC && insn instanceof MethodInsnNode method + && method.owner.equals("java/util/logging/Logger") && method.name.equals("getLogger")) { + method.owner = Type.getInternalName(LoggerTransformer.class); + transform = true; + } + } + } + return transform; + } + + public static Logger getLogger(String name) { + return JUL_MANAGER.getLogger(name); + } + + public static Logger getLogger(String name, String rb) { + return JUL_MANAGER.getLogger(name); + } +} diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightLocator_Forge.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java similarity index 93% rename from arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightLocator_Forge.java rename to arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java index 561db6e5..6d6b8c94 100644 --- a/arclight-forge/src/main/java/io/izzel/arclight/boot/ArclightLocator_Forge.java +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ArclightLocator_Forge.java @@ -1,4 +1,4 @@ -package io.izzel.arclight.boot; +package io.izzel.arclight.boot.mod; import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; @@ -26,6 +26,7 @@ public class ArclightLocator_Forge implements IModLocator { private final IModFile arclight; public ArclightLocator_Forge() { + ModBootstrap.run(); this.arclight = loadJar(); } @@ -41,6 +42,8 @@ public class ArclightLocator_Forge implements IModLocator { @Override public void scanFile(IModFile file, Consumer pathConsumer) { + // runs after TX CL built + ModBootstrap.postRun(); final Function status = p -> file.getSecureJar().verifyPath(p); try (Stream files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { file.setSecurityStatus(files.peek(pathConsumer).map(status).reduce((s1, s2) -> SecureJar.Status.values()[Math.min(s1.ordinal(), s2.ordinal())]).orElse(SecureJar.Status.INVALID)); @@ -77,6 +80,6 @@ public class ArclightLocator_Forge implements IModLocator { private JarMetadata excludePackages(SecureJar secureJar, String version) { secureJar.getPackages().removeIf(it -> EXCLUDES.stream().anyMatch(it::startsWith)); - return new SimpleJarMetadata("arclight", version.substring(version.indexOf('-')+1), secureJar.getPackages(), List.of()); + return new SimpleJarMetadata("arclight", version.substring(version.indexOf('-') + 1), secureJar.getPackages(), List.of()); } } diff --git a/arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java b/arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java new file mode 100644 index 00000000..1378d09f --- /dev/null +++ b/arclight-forge/src/main/java/io/izzel/arclight/boot/mod/ModBootstrap.java @@ -0,0 +1,134 @@ +package io.izzel.arclight.boot.mod; + +import cpw.mods.cl.JarModuleFinder; +import cpw.mods.cl.ModuleClassLoader; +import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.impl.Jar; +import cpw.mods.modlauncher.LaunchPluginHandler; +import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.serviceapi.ILaunchPluginService; +import io.izzel.arclight.api.Unsafe; +import io.izzel.arclight.boot.AbstractBootstrap; +import io.izzel.arclight.boot.asm.ArclightImplementer; +import io.izzel.arclight.forgeinstaller.ForgeInstaller; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.MarkerManager; + +import java.io.File; +import java.lang.invoke.MethodType; +import java.lang.module.Configuration; +import java.lang.module.ModuleFinder; +import java.lang.module.ResolvedModule; +import java.nio.file.Path; +import java.util.List; +import java.util.Map; +import java.util.Set; + +public class ModBootstrap extends AbstractBootstrap { + + public static record ModBoot(Configuration configuration, Thread thread, ClassLoader parent) {} + + private static volatile ModBoot modBoot; + + static void run() { + var plugin = Launcher.INSTANCE.environment().findLaunchPlugin("arclight_implementer"); + if (plugin.isPresent()) return; + var logger = LogManager.getLogger("Arclight"); + var marker = MarkerManager.getMarker("INSTALL"); + try { + var paths = ForgeInstaller.modInstall(s -> logger.info(marker, s)); + load(paths.toArray(new Path[0])); + new ModBootstrap().inject(); + } catch (Throwable e) { + logger.error("Error bootstrap Arclight", e); + throw new RuntimeException(e); + } + } + + @SuppressWarnings("unchecked") + static void postRun() { + if (modBoot == null) return; + try { + var conf = modBoot.configuration(); + var parent = modBoot.parent(); + var classLoader = (ModuleClassLoader) modBoot.thread().getContextClassLoader(); + var parentField = ModuleClassLoader.class.getDeclaredField("parentLoaders"); + var parentLoaders = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(parentField)); + for (var mod : conf.modules()) { + for (var pk : mod.reference().descriptor().packages()) { + parentLoaders.put(pk, parent); + } + } + modBoot = null; + } catch (Throwable t) { + throw new RuntimeException(t); + } + } + + private void inject() throws Throwable { + dirtyHacks(); + setupMod(); + injectClassPath(); + injectLaunchPlugin(); + } + + private void injectClassPath() throws Throwable { + var platform = ClassLoader.getPlatformClassLoader(); + var ucpField = platform.getClass().getSuperclass().getDeclaredField("ucp"); + var ucp = Unsafe.lookup().unreflectGetter(ucpField).invoke(platform); + if (ucp == null) { + for (var module : ModuleLayer.boot().configuration().modules()) { + var optional = module.reference().location(); + if (optional.isPresent()) { + var uri = optional.get(); + if (uri.getScheme().equals("file")) { + ForgeInstaller.addToPath(new File(uri).toPath()); + } + } + } + } + } + + @SuppressWarnings("unchecked") + private void injectLaunchPlugin() throws Exception { + var instance = Launcher.INSTANCE; + var launchPlugins = Launcher.class.getDeclaredField("launchPlugins"); + launchPlugins.setAccessible(true); + var handler = (LaunchPluginHandler) launchPlugins.get(instance); + var plugins = LaunchPluginHandler.class.getDeclaredField("plugins"); + plugins.setAccessible(true); + var map = (Map) plugins.get(handler); + var transformLogger = !(java.util.logging.LogManager.getLogManager() instanceof org.apache.logging.log4j.jul.LogManager); + if (transformLogger && !System.getProperties().contains("log4j.jul.LoggerAdapter")) { + System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.boot.log.ArclightLoggerAdapter"); + } + var plugin = new ArclightImplementer(transformLogger); + map.put(plugin.name(), plugin); + } + + private static final Set EXCLUDES = Set.of("org/apache/maven/artifact/repository/metadata"); + + @SuppressWarnings("unchecked") + private static void load(Path[] file) throws Throwable { + var classLoader = (ModuleClassLoader) ModBootstrap.class.getClassLoader(); + var secureJar = SecureJar.from((path, base) -> EXCLUDES.stream().noneMatch(path::startsWith), file); + var configurationField = ModuleClassLoader.class.getDeclaredField("configuration"); + var confOffset = Unsafe.objectFieldOffset(configurationField); + var oldConf = (Configuration) Unsafe.getObject(classLoader, confOffset); + var conf = oldConf.resolveAndBind(JarModuleFinder.of(secureJar), ModuleFinder.of(), List.of(secureJar.name())); + modBoot = new ModBoot(conf, Thread.currentThread(), classLoader); + Unsafe.putObjectVolatile(classLoader, confOffset, conf); + var pkgField = ModuleClassLoader.class.getDeclaredField("packageLookup"); + var packageLookup = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(pkgField)); + var rootField = ModuleClassLoader.class.getDeclaredField("resolvedRoots"); + var resolvedRoots = (Map) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(rootField)); + var moduleRefCtor = Unsafe.lookup().findConstructor(Class.forName("cpw.mods.cl.JarModuleFinder$JarModuleReference"), + MethodType.methodType(void.class, Jar.class)); + for (var mod : conf.modules()) { + for (var pk : mod.reference().descriptor().packages()) { + packageLookup.put(pk, mod); + } + resolvedRoots.put(mod.name(), moduleRefCtor.invokeWithArguments(secureJar)); + } + } +} diff --git a/arclight-forge/src/main/resources/META-INF/services/java.util.function.Consumer b/arclight-forge/src/main/resources/META-INF/services/java.util.function.Consumer index ba29b782..109fade1 100644 --- a/arclight-forge/src/main/resources/META-INF/services/java.util.function.Consumer +++ b/arclight-forge/src/main/resources/META-INF/services/java.util.function.Consumer @@ -1 +1 @@ -io.izzel.arclight.boot.ArclightBootstrap \ No newline at end of file +io.izzel.arclight.boot.application.ApplicationBootstrap \ No newline at end of file diff --git a/arclight-forge/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator b/arclight-forge/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator index 548734b0..589a95ed 100644 --- a/arclight-forge/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator +++ b/arclight-forge/src/main/resources/META-INF/services/net.minecraftforge.forgespi.locating.IModLocator @@ -1 +1 @@ -io.izzel.arclight.boot.ArclightLocator_Forge \ No newline at end of file +io.izzel.arclight.boot.mod.ArclightLocator_Forge \ No newline at end of file diff --git a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java b/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java index 44105b8c..c053b4ed 100644 --- a/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java +++ b/forge-installer/src/main/java/io/izzel/arclight/forgeinstaller/ForgeInstaller.java @@ -43,6 +43,7 @@ import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; @@ -60,7 +61,22 @@ public class ForgeInstaller { "1.17.1", "A16D67E5807F57FC4E550299CF20226194497DC2" ); - public static Map.Entry> install() throws Throwable { + public static List modInstall(Consumer logger) throws Throwable { + InputStream stream = ForgeInstaller.class.getModule().getResourceAsStream("/META-INF/installer.json"); + InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); + List> suppliers = checkMavenNoSource(installInfo.libraries); + if (!suppliers.isEmpty()) { + logger.accept("Downloading missing libraries ..."); + ExecutorService pool = Executors.newFixedThreadPool(8); + CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new); + handleFutures(logger, array); + pool.shutdownNow(); + } + return installInfo.libraries.keySet().stream().map(it -> Paths.get("libraries").resolve(Util.mavenToPath(it))).collect(Collectors.toList()); + } + + @SuppressWarnings("unused") + public static Map.Entry> applicationInstall() throws Throwable { InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json"); InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); List> suppliers = checkMavenNoSource(installInfo.libraries); @@ -68,10 +84,10 @@ public class ForgeInstaller { if (!suppliers.isEmpty() || !Files.exists(path)) { System.out.println("Downloading missing libraries ..."); ExecutorService pool = Executors.newFixedThreadPool(8); - CompletableFuture[] array = suppliers.stream().map(reportSupply(pool)).toArray(CompletableFuture[]::new); + CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, System.out::println)).toArray(CompletableFuture[]::new); if (!Files.exists(path)) { - CompletableFuture[] futures = installForge(installInfo, pool); - handleFutures(futures); + CompletableFuture[] futures = installForge(installInfo, pool, System.out::println); + handleFutures(System.out::println, futures); System.out.println("Forge installation is starting, please wait... "); try { ProcessBuilder builder = new ProcessBuilder(); @@ -88,24 +104,24 @@ public class ForgeInstaller { method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"}); } } - handleFutures(array); + handleFutures(System.out::println, array); pool.shutdownNow(); } return classpath(path, installInfo); } - private static Function, CompletableFuture> reportSupply(ExecutorService service) { + private static Function, CompletableFuture> reportSupply(ExecutorService service, Consumer logger) { return it -> CompletableFuture.supplyAsync(it, service).thenApply(path -> { - System.out.println("Downloaded " + path); + logger.accept("Downloaded " + path); return path; }); } - private static CompletableFuture[] installForge(InstallInfo info, ExecutorService pool) throws Exception { + private static CompletableFuture[] installForge(InstallInfo info, ExecutorService pool, Consumer logger) throws Exception { String format = String.format(INSTALLER_URL, info.installer.minecraft, info.installer.forge, info.installer.minecraft, info.installer.forge); String dist = String.format("forge-%s-%s-installer.jar", info.installer.minecraft, info.installer.forge); FileDownloader fd = new FileDownloader(format, dist, info.installer.hash); - CompletableFuture installerFuture = reportSupply(pool).apply(fd).thenAccept(path -> { + CompletableFuture installerFuture = reportSupply(pool, logger).apply(fd).thenAccept(path -> { try { FileSystem system = FileSystems.newFileSystem(path, (ClassLoader) null); Map> map = new HashMap<>(); @@ -114,25 +130,25 @@ public class ForgeInstaller { Path version = system.getPath("version.json"); map.putAll(profileLibraries(version)); List> suppliers = checkMaven(map); - CompletableFuture[] array = suppliers.stream().map(reportSupply(pool)).toArray(CompletableFuture[]::new); - handleFutures(array); + CompletableFuture[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new); + handleFutures(logger, array); } catch (IOException e) { e.printStackTrace(); } }); - CompletableFuture serverFuture = reportSupply(pool).apply( + CompletableFuture serverFuture = reportSupply(pool, logger).apply( new FileDownloader(String.format(SERVER_URL, info.installer.minecraft), String.format("libraries/net/minecraft/server/%1$s/minecraft_server.%1$s.jar", info.installer.minecraft), VERSION_HASH.get(info.installer.minecraft)) ); return new CompletableFuture[]{installerFuture, serverFuture}; } - private static void handleFutures(CompletableFuture... futures) { + private static void handleFutures(Consumer logger, CompletableFuture... futures) { for (CompletableFuture future : futures) { try { future.join(); } catch (CompletionException e) { - System.err.println(e.getCause().toString()); + logger.accept(e.getCause().toString()); } catch (Exception e) { e.printStackTrace(); } @@ -259,7 +275,7 @@ public class ForgeInstaller { return Map.entry(Objects.requireNonNull(mainClass, "No main class found"), userArgs); } - private static void addToPath(Path path) { + public static void addToPath(Path path) { try { ClassLoader loader = ClassLoader.getPlatformClassLoader(); Field ucpField;