Make it able to load as a mod

This commit is contained in:
IzzelAliz 2021-10-24 15:42:08 +08:00
parent 4c739f8a5a
commit 961f2d0a8a
No known key found for this signature in database
GPG Key ID: EE50E123A11D8338
18 changed files with 408 additions and 170 deletions

View File

@ -17,8 +17,12 @@ A Bukkit server implementation utilizing Mixin.
## Installing ## Installing
1. Download the jar from [release page](https://github.com/IzzelAliz/Arclight/releases) or build server. (see the table above) * Download the jar from [release page](https://github.com/IzzelAliz/Arclight/releases) or build server. (see the table
2. Launch with command `java -jar arclight-forge-<mc>-<version>.jar nogui`. The `nogui` argument will disable the server control panel. above)
* There are 2 ways to start Arclight:
* (**Recommended**) Launch with command `java -jar arclight-forge-<mc>-<version>.jar nogui`. The `nogui` argument will disable the
server control panel.
* Drop the downloaded jar into `mods` folder and start a forge server.
## Support ## Support
@ -38,9 +42,8 @@ This project is licensed under [GPL v3](LICENSE).
[![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com) [![](https://www.yourkit.com/images/yklogo.png)](https://www.yourkit.com)
YourKit supports open source projects with innovative and intelligent tools YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET
for monitoring and profiling Java and .NET applications. applications. YourKit is the creator of <a href="https://www.yourkit.com/java/profiler/">YourKit Java Profiler</a>,
YourKit is the creator of <a href="https://www.yourkit.com/java/profiler/">YourKit Java Profiler</a>,
<a href="https://www.yourkit.com/.net/profiler/">YourKit .NET Profiler</a>, <a href="https://www.yourkit.com/.net/profiler/">YourKit .NET Profiler</a>,
and <a href="https://www.yourkit.com/youmonitor/">YourKit YouMonitor</a>. and <a href="https://www.yourkit.com/youmonitor/">YourKit YouMonitor</a>.

View File

@ -1,16 +1,25 @@
package io.izzel.arclight.common.mixin.bukkit; 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.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.bukkit.craftbukkit.v.command.ColouredConsoleSender; import org.bukkit.craftbukkit.v.command.ColouredConsoleSender;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite; 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) @Mixin(value = ColouredConsoleSender.class, remap = false)
public class ColouredConsoleSenderMixin extends CraftConsoleCommandSenderMixin { public class ColouredConsoleSenderMixin extends CraftConsoleCommandSenderMixin {
private static final Logger LOGGER = LogManager.getLogger("Console"); private static final Logger LOGGER = LogManager.getLogger("Console");
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljline/console/ConsoleReader;getTerminal()Ljline/Terminal;"))
private Terminal arclight$terminal(ConsoleReader instance) {
return null;
}
/** /**
* @author IzzelAliz * @author IzzelAliz
* @reason use TerminalConsoleAppender * @reason use TerminalConsoleAppender

View File

@ -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.CallbackInfo;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable; import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.io.IOException;
import java.util.Locale; import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -83,12 +82,8 @@ public abstract class CraftServerMixin implements CraftServerBridge {
*/ */
@Overwrite(remap = false) @Overwrite(remap = false)
public ConsoleReader getReader() { 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;")) @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;"))
private void arclight$unloadForge(World world, boolean save, CallbackInfoReturnable<Boolean> cir) { private void arclight$unloadForge(World world, boolean save, CallbackInfoReturnable<Boolean> cir) {

View File

@ -1,20 +1,23 @@
package io.izzel.arclight.common.mod.util.log; package io.izzel.arclight.common.mod.util.log;
import org.apache.logging.log4j.jul.LogManager;
import org.bukkit.plugin.Plugin; import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginLogger; import org.bukkit.plugin.PluginLogger;
import java.util.logging.LogManager;
import java.util.logging.LogRecord; import java.util.logging.LogRecord;
import java.util.logging.Logger; import java.util.logging.Logger;
public class ArclightPluginLogger extends PluginLogger { 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; private final Logger logger;
public ArclightPluginLogger(Plugin context) { public ArclightPluginLogger(Plugin context) {
super(context); super(context);
String prefix = context.getDescription().getPrefix(); String prefix = context.getDescription().getPrefix();
logger = LogManager.getLogManager().getLogger(prefix == null ? context.getName() : prefix); logger = JUL_MANAGER.getLogger(prefix == null ? context.getName() : prefix);
} }
@Override @Override

View File

@ -25,7 +25,8 @@ java.toolchain.languageVersion = JavaLanguageVersion.of(16)
configurations { configurations {
installer installer
embed embed
implementation.extendsFrom(embed) gson
implementation.extendsFrom(embed, gson)
} }
repositories { repositories {
@ -40,12 +41,12 @@ repositories {
maven { url = 'https://maven.izzel.io/releases' } 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', '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', /*'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-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', '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.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', '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: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-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.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.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.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', 'org.spongepowered:configurate-hocon:3.6.1', 'org.spongepowered:configurate-core:3.6.1',
'com.typesafe:config:1.3.1'] 'com.typesafe:config:1.3.1']
@ -66,11 +67,11 @@ dependencies {
implementation 'cpw.mods:modlauncher:9.0.7' implementation 'cpw.mods:modlauncher:9.0.7'
implementation 'cpw.mods:securejarhandler:0.9.45' implementation 'cpw.mods:securejarhandler:0.9.45'
implementation 'net.minecraftforge:forgespi:4.0.9' 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.apache.logging.log4j:log4j-api:2.14.1'
implementation 'org.jetbrains:annotations:19.0.0' implementation 'org.jetbrains:annotations:19.0.0'
implementation 'org.spongepowered:mixin:0.8.3' 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) { for (def lib : embedLibs) {
installer lib installer lib
} }
@ -81,7 +82,9 @@ dependencies {
embed(project(':i18n-config')) { embed(project(':i18n-config')) {
transitive = false transitive = false
} }
embed(project(':forge-installer')) embed(project(':forge-installer')) {
transitive = false
}
} }
jar { jar {
@ -104,6 +107,10 @@ jar {
it.from(project(':arclight-common').tasks.jar.outputs.files.collect()) it.from(project(':arclight-common').tasks.jar.outputs.files.collect())
it.rename { name -> 'common.jar' } it.rename { name -> 'common.jar' }
} }
into('/') {
it.from(configurations.gson.collect())
it.rename { name -> 'gson.jar' }
}
from sourceSets.applaunch.output.classesDirs from sourceSets.applaunch.output.classesDirs
duplicatesStrategy = DuplicatesStrategy.EXCLUDE duplicatesStrategy = DuplicatesStrategy.EXCLUDE
dependsOn(project(':arclight-common').tasks.jar) dependsOn(project(':arclight-common').tasks.jar)

View File

@ -1,6 +1,6 @@
package io.izzel.arclight.server; package io.izzel.arclight.server;
import io.izzel.arclight.boot.Main_Forge; import io.izzel.arclight.boot.application.Main_Forge;
public class Launcher { public class Launcher {

View File

@ -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);
}
}
}

View File

@ -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<String[]> {
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);
}
}
}

View File

@ -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<String, List<String>> 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);
}
}
}

View File

@ -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<String[]> {
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.");
}
}
}

View File

@ -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<String, List<String>> 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<String, List<String>> 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<String, List<String>>) handle.invoke();
}
}
}

View File

@ -29,6 +29,15 @@ public class ArclightImplementer implements ILaunchPluginService {
private final Map<String, Implementer> implementers = new HashMap<>(); private final Map<String, Implementer> implementers = new HashMap<>();
private volatile Consumer<String[]> auditAcceptor; private volatile Consumer<String[]> auditAcceptor;
private ITransformerLoader transformerLoader; private ITransformerLoader transformerLoader;
private final boolean logger;
public ArclightImplementer() {
this(false);
}
public ArclightImplementer(boolean logger) {
this.logger = logger;
}
@Override @Override
public String name() { public String name() {
@ -42,6 +51,9 @@ public class ArclightImplementer implements ILaunchPluginService {
this.implementers.put("switch", SwitchTableFixer.INSTANCE); this.implementers.put("switch", SwitchTableFixer.INSTANCE);
this.implementers.put("async", AsyncCatcher.INSTANCE); this.implementers.put("async", AsyncCatcher.INSTANCE);
this.implementers.put("entitytype", EntityTypePatcher.INSTANCE); this.implementers.put("entitytype", EntityTypePatcher.INSTANCE);
if (this.logger) {
this.implementers.put("logger", new LoggerTransformer());
}
} }
@Override @Override

View File

@ -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);
}
}

View File

@ -1,4 +1,4 @@
package io.izzel.arclight.boot; package io.izzel.arclight.boot.mod;
import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.JarMetadata;
import cpw.mods.jarhandling.SecureJar; import cpw.mods.jarhandling.SecureJar;
@ -26,6 +26,7 @@ public class ArclightLocator_Forge implements IModLocator {
private final IModFile arclight; private final IModFile arclight;
public ArclightLocator_Forge() { public ArclightLocator_Forge() {
ModBootstrap.run();
this.arclight = loadJar(); this.arclight = loadJar();
} }
@ -41,6 +42,8 @@ public class ArclightLocator_Forge implements IModLocator {
@Override @Override
public void scanFile(IModFile file, Consumer<Path> pathConsumer) { public void scanFile(IModFile file, Consumer<Path> pathConsumer) {
// runs after TX CL built
ModBootstrap.postRun();
final Function<Path, SecureJar.Status> status = p -> file.getSecureJar().verifyPath(p); final Function<Path, SecureJar.Status> status = p -> file.getSecureJar().verifyPath(p);
try (Stream<Path> files = Files.find(file.getSecureJar().getRootPath(), Integer.MAX_VALUE, (p, a) -> p.getNameCount() > 0 && p.getFileName().toString().endsWith(".class"))) { try (Stream<Path> 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)); 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) { private JarMetadata excludePackages(SecureJar secureJar, String version) {
secureJar.getPackages().removeIf(it -> EXCLUDES.stream().anyMatch(it::startsWith)); 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());
} }
} }

View File

@ -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<String, ClassLoader>) 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<String, ILaunchPluginService>) 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<String> 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<String, ResolvedModule>) Unsafe.getObject(classLoader, Unsafe.objectFieldOffset(pkgField));
var rootField = ModuleClassLoader.class.getDeclaredField("resolvedRoots");
var resolvedRoots = (Map<String, Object>) 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));
}
}
}

View File

@ -1 +1 @@
io.izzel.arclight.boot.ArclightBootstrap io.izzel.arclight.boot.application.ApplicationBootstrap

View File

@ -1 +1 @@
io.izzel.arclight.boot.ArclightLocator_Forge io.izzel.arclight.boot.mod.ArclightLocator_Forge

View File

@ -43,6 +43,7 @@ import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException; import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.function.Consumer;
import java.util.function.Function; import java.util.function.Function;
import java.util.function.Supplier; import java.util.function.Supplier;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@ -60,7 +61,22 @@ public class ForgeInstaller {
"1.17.1", "A16D67E5807F57FC4E550299CF20226194497DC2" "1.17.1", "A16D67E5807F57FC4E550299CF20226194497DC2"
); );
public static Map.Entry<String, List<String>> install() throws Throwable { public static List<Path> modInstall(Consumer<String> logger) throws Throwable {
InputStream stream = ForgeInstaller.class.getModule().getResourceAsStream("/META-INF/installer.json");
InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class);
List<Supplier<Path>> 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<String, List<String>> applicationInstall() throws Throwable {
InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json"); InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json");
InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class); InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class);
List<Supplier<Path>> suppliers = checkMavenNoSource(installInfo.libraries); List<Supplier<Path>> suppliers = checkMavenNoSource(installInfo.libraries);
@ -68,10 +84,10 @@ public class ForgeInstaller {
if (!suppliers.isEmpty() || !Files.exists(path)) { if (!suppliers.isEmpty() || !Files.exists(path)) {
System.out.println("Downloading missing libraries ..."); System.out.println("Downloading missing libraries ...");
ExecutorService pool = Executors.newFixedThreadPool(8); 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)) { if (!Files.exists(path)) {
CompletableFuture<?>[] futures = installForge(installInfo, pool); CompletableFuture<?>[] futures = installForge(installInfo, pool, System.out::println);
handleFutures(futures); handleFutures(System.out::println, futures);
System.out.println("Forge installation is starting, please wait... "); System.out.println("Forge installation is starting, please wait... ");
try { try {
ProcessBuilder builder = new ProcessBuilder(); ProcessBuilder builder = new ProcessBuilder();
@ -88,24 +104,24 @@ public class ForgeInstaller {
method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"}); method.invoke(null, (Object) new String[]{"--installServer", ".", "--debug"});
} }
} }
handleFutures(array); handleFutures(System.out::println, array);
pool.shutdownNow(); pool.shutdownNow();
} }
return classpath(path, installInfo); return classpath(path, installInfo);
} }
private static Function<Supplier<Path>, CompletableFuture<Path>> reportSupply(ExecutorService service) { private static Function<Supplier<Path>, CompletableFuture<Path>> reportSupply(ExecutorService service, Consumer<String> logger) {
return it -> CompletableFuture.supplyAsync(it, service).thenApply(path -> { return it -> CompletableFuture.supplyAsync(it, service).thenApply(path -> {
System.out.println("Downloaded " + path); logger.accept("Downloaded " + path);
return path; return path;
}); });
} }
private static CompletableFuture<?>[] installForge(InstallInfo info, ExecutorService pool) throws Exception { private static CompletableFuture<?>[] installForge(InstallInfo info, ExecutorService pool, Consumer<String> logger) throws Exception {
String format = String.format(INSTALLER_URL, info.installer.minecraft, info.installer.forge, info.installer.minecraft, info.installer.forge); 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); String dist = String.format("forge-%s-%s-installer.jar", info.installer.minecraft, info.installer.forge);
FileDownloader fd = new FileDownloader(format, dist, info.installer.hash); 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 { try {
FileSystem system = FileSystems.newFileSystem(path, (ClassLoader) null); FileSystem system = FileSystems.newFileSystem(path, (ClassLoader) null);
Map<String, Map.Entry<String, String>> map = new HashMap<>(); Map<String, Map.Entry<String, String>> map = new HashMap<>();
@ -114,25 +130,25 @@ public class ForgeInstaller {
Path version = system.getPath("version.json"); Path version = system.getPath("version.json");
map.putAll(profileLibraries(version)); map.putAll(profileLibraries(version));
List<Supplier<Path>> suppliers = checkMaven(map); List<Supplier<Path>> suppliers = checkMaven(map);
CompletableFuture<?>[] array = suppliers.stream().map(reportSupply(pool)).toArray(CompletableFuture[]::new); CompletableFuture<?>[] array = suppliers.stream().map(reportSupply(pool, logger)).toArray(CompletableFuture[]::new);
handleFutures(array); handleFutures(logger, array);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
}); });
CompletableFuture<?> serverFuture = reportSupply(pool).apply( CompletableFuture<?> serverFuture = reportSupply(pool, logger).apply(
new FileDownloader(String.format(SERVER_URL, info.installer.minecraft), 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)) 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}; return new CompletableFuture<?>[]{installerFuture, serverFuture};
} }
private static void handleFutures(CompletableFuture<?>... futures) { private static void handleFutures(Consumer<String> logger, CompletableFuture<?>... futures) {
for (CompletableFuture<?> future : futures) { for (CompletableFuture<?> future : futures) {
try { try {
future.join(); future.join();
} catch (CompletionException e) { } catch (CompletionException e) {
System.err.println(e.getCause().toString()); logger.accept(e.getCause().toString());
} catch (Exception e) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -259,7 +275,7 @@ public class ForgeInstaller {
return Map.entry(Objects.requireNonNull(mainClass, "No main class found"), userArgs); return Map.entry(Objects.requireNonNull(mainClass, "No main class found"), userArgs);
} }
private static void addToPath(Path path) { public static void addToPath(Path path) {
try { try {
ClassLoader loader = ClassLoader.getPlatformClassLoader(); ClassLoader loader = ClassLoader.getPlatformClassLoader();
Field ucpField; Field ucpField;