Make it able to load as a mod
This commit is contained in:
parent
4c739f8a5a
commit
961f2d0a8a
13
README.md
13
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-<mc>-<version>.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-<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
|
||||
|
||||
|
@ -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 <a href="https://www.yourkit.com/java/profiler/">YourKit Java Profiler</a>,
|
||||
YourKit supports open source projects with innovative and intelligent tools for monitoring and profiling Java and .NET
|
||||
applications. 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>,
|
||||
and <a href="https://www.yourkit.com/youmonitor/">YourKit YouMonitor</a>.
|
||||
|
||||
|
|
|
@ -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 = "<init>", at = @At(value = "INVOKE", target = "Ljline/console/ConsoleReader;getTerminal()Ljline/Terminal;"))
|
||||
private Terminal arclight$terminal(ConsoleReader instance) {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @author IzzelAliz
|
||||
* @reason use TerminalConsoleAppender
|
||||
|
|
|
@ -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,12 +82,8 @@ public abstract class CraftServerMixin implements CraftServerBridge {
|
|||
*/
|
||||
@Overwrite(remap = false)
|
||||
public ConsoleReader getReader() {
|
||||
try {
|
||||
return new ConsoleReader();
|
||||
} catch (IOException e) {
|
||||
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;"))
|
||||
private void arclight$unloadForge(World world, boolean save, CallbackInfoReturnable<Boolean> cir) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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.");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -29,6 +29,15 @@ public class ArclightImplementer implements ILaunchPluginService {
|
|||
private final Map<String, Implementer> implementers = new HashMap<>();
|
||||
private volatile Consumer<String[]> 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
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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<Path> pathConsumer) {
|
||||
// runs after TX CL built
|
||||
ModBootstrap.postRun();
|
||||
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"))) {
|
||||
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());
|
||||
}
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1 +1 @@
|
|||
io.izzel.arclight.boot.ArclightBootstrap
|
||||
io.izzel.arclight.boot.application.ApplicationBootstrap
|
|
@ -1 +1 @@
|
|||
io.izzel.arclight.boot.ArclightLocator_Forge
|
||||
io.izzel.arclight.boot.mod.ArclightLocator_Forge
|
|
@ -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<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");
|
||||
InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class);
|
||||
List<Supplier<Path>> 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<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 -> {
|
||||
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<String> 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<String, Map.Entry<String, String>> map = new HashMap<>();
|
||||
|
@ -114,25 +130,25 @@ public class ForgeInstaller {
|
|||
Path version = system.getPath("version.json");
|
||||
map.putAll(profileLibraries(version));
|
||||
List<Supplier<Path>> 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<String> 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;
|
||||
|
|
Loading…
Reference in New Issue
Block a user