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
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>.

View File

@ -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

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.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;"))

View File

@ -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

View File

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

View File

@ -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 {

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 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

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.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());
}
}

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.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;