Forge installer. Close #7. Close #3.

This commit is contained in:
IzzelAliz 2020-05-21 10:33:47 +08:00
parent 5302b8f008
commit 64a9037bcb
17 changed files with 452 additions and 146 deletions

14
arclight-api/build.gradle Normal file
View File

@ -0,0 +1,14 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
}
compileJava {
options.compilerArgs << '-XDignore.symbol.file' << '-XDenableSunApiLintControl'
}

View File

@ -1,4 +1,4 @@
package io.izzel.arclight.util;
package io.izzel.arclight.api;
import sun.reflect.CallerSensitive;

View File

@ -0,0 +1,11 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
compile project(':arclight-api')
}

View File

@ -16,6 +16,13 @@ apply plugin: 'org.spongepowered.mixin'
apply plugin: 'java'
apply plugin: 'idea'
ext {
minecraftVersion = '1.14.4'
forgeVersion = '28.2.0'
installerInfoDir = file("$buildDir/installer-info")
installerInfo = file("$installerInfoDir/META-INF/installer.json")
}
sourceCompatibility = targetCompatibility = compileJava.sourceCompatibility = compileJava.targetCompatibility = '1.8'
configurations {
@ -25,7 +32,7 @@ configurations {
}
minecraft {
mappings channel: 'stable', version: '58-1.14.4'
mappings channel: 'stable', version: "58-$minecraftVersion"
accessTransformer = project.file('src/main/resources/META-INF/accesstransformer.cfg')
runs {
server {
@ -55,19 +62,21 @@ repositories {
def embedLibs = ['org.spongepowered:mixin:0.8', 'org.ow2.asm:asm-util:6.2',
'org.ow2.asm:asm-analysis:6.2', 'org.yaml:snakeyaml:1.23',
'net.md-5:bungeecord-chat:1.13-SNAPSHOT', 'org.xerial:sqlite-jdbc:3.28.0',
'mysql:mysql-connector-java:5.1.47', 'commons-lang:commons-lang:2.6',
'jline:jline:2.12.1', 'com.googlecode.json-simple:json-simple:1.1.1',
'org.apache.logging.log4j:log4j-jul:2.11.2', 'net.md-5:SpecialSource:1.8.6',
'net.minecraftforge:eventbus:2.0.0-milestone.1:service']
'org.xerial:sqlite-jdbc:3.28.0', 'mysql:mysql-connector-java:5.1.47',
'commons-lang:commons-lang:2.6', 'jline:jline:2.12.1',
'com.googlecode.json-simple:json-simple:1.1.1', 'org.apache.logging.log4j:log4j-jul:2.11.2',
'net.md-5:SpecialSource:1.8.6', 'net.minecraftforge:eventbus:2.0.0-milestone.1:service']
dependencies {
minecraft 'net.minecraftforge:forge:1.14.4-28.2.0'
minecraft "net.minecraftforge:forge:$minecraftVersion-$forgeVersion"
compile group: 'org.jetbrains', name: 'annotations', version: '19.0.0'
embed project(':arclight-common')
embed project(':forge-installer')
for (def lib : embedLibs) {
embedJar "$lib@jar"
}
embed 'org.spigotmc:spigot-api:1.14.4-R0.1-SNAPSHOT@jar'
embed 'net.md-5:bungeecord-chat:1.13-SNAPSHOT@jar'
embed "org.spigotmc:spigot-api:$minecraftVersion-R0.1-SNAPSHOT@jar"
embed files("$projectDir/libs/spigot-1.14.4-mapped-deobf.jar")
}
@ -82,26 +91,13 @@ def getGitHash = { ->
processResources {
filesNotMatching("**/accesstransformer.cfg") {
expand 'version': "1.14.4-${project.version}-${getGitHash()}"
expand 'version': "$minecraftVersion-${project.version}-${getGitHash()}"
}
}
def classpath = {
"libraries/org/ow2/asm/asm/6.2/asm-6.2.jar libraries/org/ow2/asm/asm-commons/6.2/asm-commons-6.2.jar libraries/org/ow2/asm/asm-tree/6.2/asm-tree-6.2.jar libraries/cpw/mods/modlauncher/4.1.0/modlauncher-4.1.0.jar libraries/cpw/mods/grossjava9hacks/1.1.0/grossjava9hacks-1.1.0.jar libraries/net/minecraftforge/accesstransformers/1.0.1-milestone.0.1+94458e7-shadowed/accesstransformers-1.0.1-milestone.0.1+94458e7-shadowed.jar libraries/net/minecraftforge/forgespi/1.5.0/forgespi-1.5.0.jar libraries/net/minecraftforge/coremods/1.0.0/coremods-1.0.0.jar libraries/net/minecraftforge/unsafe/0.2.0/unsafe-0.2.0.jar libraries/com/electronwill/night-config/core/3.6.0/core-3.6.0.jar libraries/com/electronwill/night-config/toml/3.6.0/toml-3.6.0.jar libraries/org/jline/jline/3.12.1/jline-3.12.1.jar libraries/org/apache/maven/maven-artifact/3.6.0/maven-artifact-3.6.0.jar libraries/net/jodah/typetools/0.6.0/typetools-0.6.0.jar libraries/java3d/vecmath/1.5.2/vecmath-1.5.2.jar libraries/org/apache/logging/log4j/log4j-api/2.11.2/log4j-api-2.11.2.jar libraries/org/apache/logging/log4j/log4j-core/2.11.2/log4j-core-2.11.2.jar libraries/net/minecrell/terminalconsoleappender/1.2.0/terminalconsoleappender-1.2.0.jar libraries/net/sf/jopt-simple/jopt-simple/5.0.4/jopt-simple-5.0.4.jar libraries/net/minecraft/server/1.14.4/server-1.14.4-extra-stable.jar " +
embedLibs.collect {
def arr = it.split(':')
if (arr.length == 3) {
return "libraries/${arr[0].replace('.', '/')}/${arr[1]}/${arr[2]}/${arr[1]}-${arr[2]}.jar"
} else if (arr.length == 4) {
return "libraries/${arr[0].replace('.', '/')}/${arr[1]}/${arr[2]}/${arr[1]}-${arr[2]}-${arr[3]}.jar"
} else return ""
}.join(' ') + " forge-1.14.4-28.2.0.jar"
}
jar {
manifest.attributes 'MixinConnector': 'io.izzel.arclight.mod.ArclightConnector'
manifest.attributes 'Main-Class': 'io.izzel.arclight.server.Main'
manifest.attributes 'Class-Path': classpath()
manifest.attributes 'Implementation-Title': 'Arclight'
manifest.attributes 'Implementation-Version': "arclight-${project.version}-${getGitHash()}"
manifest.attributes 'Implementation-Vendor': 'Arclight Team'
@ -112,15 +108,22 @@ jar {
exclude "META-INF/*.RSA"
exclude "LICENSE.txt"
}
into('libs') {
from(configurations.embedJar.collect())
}
into('META-INF') {
from(files("${project(':scripts').projectDir}/bukkit_srg.srg"))
from(files("${project(':scripts').projectDir}/resources/inheritanceMap.txt"))
}
}
task generateInstallerInfo {
def output = [installer: [minecraft: minecraftVersion, forge: forgeVersion], libraries: embedLibs]
outputs.file(installerInfo)
doLast {
installerInfo.text = groovy.json.JsonOutput.toJson(output)
}
}
sourceSets.main.output.dir installerInfoDir, builtBy: generateInstallerInfo
mixin {
add sourceSets.main, 'mixins.arclight.refmap.json'
}

View File

@ -0,0 +1,45 @@
package io.izzel.arclight.mixin.core.state;
import net.minecraft.state.EnumProperty;
import net.minecraft.util.IStringSerializable;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Overwrite;
import org.spongepowered.asm.mixin.Shadow;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.function.Predicate;
@Mixin(EnumProperty.class)
public abstract class EnumPropertyMixin {
@Shadow
public static <T extends Enum<T> & IStringSerializable> EnumProperty<T> create(String name, Class<T> clazz, Collection<T> values) {
return null;
}
/**
* @author IzzelAliz
* @reason
*/
@Overwrite
public static <T extends Enum<T> & IStringSerializable> EnumProperty<T> create(String name, Class<T> clazz, Predicate<T> filter) {
try {
List<T> list = new ArrayList<>();
for (T enumConstant : clazz.getEnumConstants()) {
if (filter.test(enumConstant)) list.add(enumConstant);
}
return create(name, clazz, list);
} catch (Throwable t) {
System.out.println(name);
System.out.println(clazz);
System.out.println(filter);
for (T constant : clazz.getEnumConstants()) {
System.out.println(constant);
}
t.printStackTrace();
throw t;
}
}
}

View File

@ -6,7 +6,7 @@ import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import io.izzel.arclight.util.Unsafe;
import io.izzel.arclight.api.Unsafe;
import java.lang.reflect.Field;
import java.util.HashSet;

View File

@ -19,7 +19,7 @@ import io.izzel.arclight.bridge.bukkit.MaterialBridge;
import io.izzel.arclight.mod.ArclightMod;
import io.izzel.arclight.mod.util.potion.ArclightPotionEffect;
import io.izzel.arclight.util.EnumHelper;
import io.izzel.arclight.util.Unsafe;
import io.izzel.arclight.api.Unsafe;
import java.lang.reflect.Field;
import java.util.ArrayList;

View File

@ -2,7 +2,7 @@ package io.izzel.arclight.mod.util.remapper;
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import io.izzel.arclight.util.Unsafe;
import io.izzel.arclight.api.Unsafe;
import net.md_5.specialsource.InheritanceMap;
import net.md_5.specialsource.JarMapping;
import net.md_5.specialsource.provider.ClassLoaderProvider;

View File

@ -5,7 +5,7 @@ import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;
import com.google.common.collect.Maps;
import io.izzel.arclight.mod.util.remapper.generated.ArclightReflectionHandler;
import io.izzel.arclight.util.Unsafe;
import io.izzel.arclight.api.Unsafe;
import net.md_5.specialsource.JarMapping;
import net.md_5.specialsource.JarRemapper;
import net.md_5.specialsource.RemappingClassAdapter;

View File

@ -1,32 +1,25 @@
package io.izzel.arclight.server;
import com.google.common.io.ByteStreams;
import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.forgeinstaller.ForgeInstaller;
import io.izzel.arclight.mod.util.BukkitOptionParser;
import io.izzel.arclight.mod.util.remapper.ArclightRemapper;
import io.izzel.arclight.util.EnumHelper;
import io.izzel.arclight.util.Unsafe;
import joptsimple.OptionSet;
import net.minecraftforge.server.ServerMain;
import org.apache.logging.log4j.LogManager;
import org.fusesource.jansi.AnsiConsole;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Objects;
public class Main {
public static void main(String[] args) throws Throwable {
if (Files.notExists(Paths.get("forge-1.14.4-28.2.0.jar"))) {
System.err.println("Install forge 1.14.4-28.2.0 before launching Arclight.");
return;
ForgeInstaller.install();
for (URL url : ((URLClassLoader) Main.class.getClassLoader()).getURLs()) {
System.out.println(url);
}
try { // Java 9 & Java 兼容性
int javaVersion = (int) Float.parseFloat(System.getProperty("java.class.version"));
@ -40,114 +33,34 @@ public class Main {
return;
}
try {
if (Files.notExists(Paths.get("./libraries/net/minecraftforge/eventbus/2.0.0-milestone.1/eventbus-2.0.0-milestone.1-service.jar"))) {
Path folder = Paths.get("./libraries/net/minecraftforge/eventbus");
Files.walkFileTree(folder, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
Files.delete(file);
return FileVisitResult.CONTINUE;
}
OptionSet options = new BukkitOptionParser().parse(args);
String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
@Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
Files.delete(dir);
return FileVisitResult.CONTINUE;
}
});
throw new Exception();
boolean useJline = !(jline_UnsupportedTerminal).equals(System.getProperty(jline_terminal));
if (options.has("nojline")) {
System.setProperty("user.language", "en");
useJline = false;
}
Class.forName("org.spongepowered.asm.mixin.Mixins", false, ClassLoader.getSystemClassLoader());
Class.forName("org.objectweb.asm.util.CheckClassAdapter", false, ClassLoader.getSystemClassLoader());
Class.forName("org.objectweb.asm.tree.analysis.AnalyzerException", false, ClassLoader.getSystemClassLoader());
Class.forName("net.md_5.bungee.api.ChatColor", false, ClassLoader.getSystemClassLoader());
Class.forName("org.yaml.snakeyaml.Yaml", false, ClassLoader.getSystemClassLoader());
Class.forName("org.sqlite.JDBC", false, ClassLoader.getSystemClassLoader());
Class.forName("com.mysql.jdbc.Driver", false, ClassLoader.getSystemClassLoader());
Class.forName("org.apache.commons.lang.Validate", false, ClassLoader.getSystemClassLoader());
Class.forName("jline.Terminal", false, ClassLoader.getSystemClassLoader());
Class.forName("org.json.simple.JSONObject", false, ClassLoader.getSystemClassLoader());
Class.forName("org.apache.logging.log4j.jul.LogManager", false, ClassLoader.getSystemClassLoader());
Class.forName("net.minecraftforge.eventbus.EventBus", false, ClassLoader.getSystemClassLoader());
Class.forName("net.md_5.specialsource.JarRemapper", false, ClassLoader.getSystemClassLoader());
try {
OptionSet options = new BukkitOptionParser().parse(args);
String jline_UnsupportedTerminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 'U', 'n', 's', 'u', 'p', 'p', 'o', 'r', 't', 'e', 'd', 'T', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
String jline_terminal = new String(new char[]{'j', 'l', 'i', 'n', 'e', '.', 't', 'e', 'r', 'm', 'i', 'n', 'a', 'l'});
boolean useJline = !(jline_UnsupportedTerminal).equals(System.getProperty(jline_terminal));
if (options.has("nojline")) {
System.setProperty("user.language", "en");
useJline = false;
}
if (useJline) {
AnsiConsole.systemInstall();
} else {
System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName());
}
} catch (Exception e) {
e.printStackTrace();
}
try {
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.mod.util.ArclightLoggerAdapter");
LogManager.getLogger("Arclight").info("Loading mappings ...");
Objects.requireNonNull(ArclightRemapper.INSTANCE);
ServerMain.main(args);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Fail to launch Arclight.");
if (useJline) {
AnsiConsole.systemInstall();
} else {
System.setProperty(jline.TerminalFactory.JLINE_TERMINAL, jline.UnsupportedTerminal.class.getName());
}
} catch (Exception e) {
System.err.println("FATAL ERROR: The libraries required to launch Arclight are missing, extracting...");
extract("org.spongepowered:mixin:0.8");
extract("org.ow2.asm:asm-util:6.2");
extract("org.ow2.asm:asm-analysis:6.2");
extract("org.yaml:snakeyaml:1.23");
extract("net.md-5:bungeecord-chat:1.13-SNAPSHOT");
extract("org.xerial:sqlite-jdbc:3.28.0");
extract("mysql:mysql-connector-java:5.1.47");
extract("commons-lang:commons-lang:2.6");
extract("jline:jline:2.12.1");
extract("com.googlecode.json-simple:json-simple:1.1.1");
extract("org.apache.logging.log4j:log4j-jul:2.11.2");
extract("net.md-5:SpecialSource:1.8.6");
extract("net.minecraftforge:eventbus:2.0.0-milestone.1:service");
System.out.println("Please RESTART the server.");
e.printStackTrace();
}
}
private static void extract(String artifact) throws Throwable {
String[] split = artifact.split(":");
if (split.length == 3) {
String jar = String.format("/%s-%s.jar", split[1], split[2]);
String path = split[0].replace('.', '/') + "/" +
split[1] + "/" + split[2] + jar;
extract("libs" + jar, "./libraries/" + path);
System.out.println("Extracted " + artifact);
} else if (split.length == 4) {
String jar = String.format("/%s-%s-%s.jar", split[1], split[2], split[3]);
String path = split[0].replace('.', '/') + "/" +
split[1] + "/" + split[2] + jar;
extract("libs" + jar, "./libraries/" + path);
System.out.println("Extracted " + artifact);
}
}
private static void extract(String name, String target) throws Throwable {
Path path = Paths.get(target);
if (Files.notExists(path)) {
Files.createDirectories(path.getParent());
Files.createFile(path);
InputStream stream = Main.class.getResourceAsStream("/" + name);
if (stream != null) {
OutputStream outputStream = Files.newOutputStream(Paths.get(target));
ByteStreams.copy(stream, outputStream);
stream.close();
outputStream.close();
}
try {
System.setProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager");
System.setProperty("log4j.jul.LoggerAdapter", "io.izzel.arclight.mod.util.ArclightLoggerAdapter");
LogManager.getLogger("Arclight").info("Loading mappings ...");
Objects.requireNonNull(ArclightRemapper.INSTANCE);
ServerMain.main(args);
} catch (Exception e) {
e.printStackTrace();
System.err.println("Fail to launch Arclight.");
}
}
}

View File

@ -1,6 +1,7 @@
package io.izzel.arclight.util;
import com.google.common.collect.ImmutableList;
import io.izzel.arclight.api.Unsafe;
import org.bukkit.Material;
import java.lang.invoke.MethodHandle;

View File

@ -321,6 +321,7 @@
"server.management.PlayerInteractionManagerMixin",
"server.management.PlayerListMixin",
"server.management.UserListMixin",
"state.EnumPropertyMixin",
"state.IntegerPropertyMixin",
"stats.StatisticsManagerMixin",
"tags.NetworkTagCollectionMixin",

View File

@ -0,0 +1,12 @@
plugins {
id 'java'
}
repositories {
mavenCentral()
}
dependencies {
compile 'com.google.code.gson:gson:2.8.0'
compile project(':arclight-api')
}

View File

@ -0,0 +1,138 @@
package io.izzel.arclight.forgeinstaller;
import com.google.gson.Gson;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import io.izzel.arclight.api.Unsafe;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URL;
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.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
public class ForgeInstaller {
private static final String INFO = "Download mirror service by BMCLAPI: https://bmclapidoc.bangbang93.com\n" +
"Support MinecraftForge project at https://www.patreon.com/LexManos/";
private static final String INSTALLER_URL = "https://bmclapi2.bangbang93.com/maven/net/minecraftforge/forge/%s-%s/forge-%s-%s-installer.jar";
private static final String BMCL_API = "https://bmclapi2.bangbang93.com/maven/";
private static final String SERVER_URL = "https://bmclapi2.bangbang93.com/version/%s/server";
public static void install() throws Throwable {
InputStream stream = ForgeInstaller.class.getResourceAsStream("/META-INF/installer.json");
InstallInfo installInfo = new Gson().fromJson(new InputStreamReader(stream), InstallInfo.class);
Path path = Paths.get(String.format("forge-%s-%s.jar", installInfo.installer.minecraft, installInfo.installer.forge));
if (!Files.exists(path)) {
System.out.println(INFO);
Thread.sleep(5000);
download(installInfo);
ProcessBuilder builder = new ProcessBuilder();
builder.command("java", "-jar", String.format("forge-%s-%s-installer.jar", installInfo.installer.minecraft, installInfo.installer.forge), "--installServer", ".");
builder.inheritIO();
Process process = builder.start();
process.waitFor();
}
classpath(path, installInfo);
}
private static void classpath(Path path, InstallInfo installInfo) throws Throwable {
JarFile jarFile = new JarFile(path.toFile());
Manifest manifest = jarFile.getManifest();
String[] split = manifest.getMainAttributes().getValue("Class-Path").split(" ");
for (String s : split) {
if (s.contains("eventbus-1.0.0-service")) continue;
addToPath(Paths.get(s));
}
for (String library : installInfo.libraries) {
addToPath(Paths.get("libraries", mavenToPath(library)));
}
addToPath(path);
}
private static void addToPath(Path path) throws Throwable {
ClassLoader loader = ForgeInstaller.class.getClassLoader();
Field ucpField = loader.getClass().getDeclaredField("ucp");
long offset = Unsafe.objectFieldOffset(ucpField);
Object ucp = Unsafe.getObject(loader, offset);
Method method = ucp.getClass().getDeclaredMethod("addURL", URL.class);
Unsafe.lookup().unreflect(method).invoke(ucp, path.toUri().toURL());
}
private static void download(InstallInfo info) throws Exception {
SimpleDownloader downloader = new SimpleDownloader();
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);
downloader.download(format, dist, null,
path -> processInstaller(path, downloader));
for (String library : info.libraries) {
String path = mavenToPath(library);
downloader.downloadMaven(path);
}
downloader.download(String.format(SERVER_URL, info.installer.minecraft), String.format("minecraft_server.%s.jar", info.installer.minecraft), null);
if (!downloader.awaitTermination()) {
Files.deleteIfExists(Paths.get(dist));
throw new Exception();
}
}
private static void processInstaller(Path path, SimpleDownloader downloader) {
try {
FileSystem system = FileSystems.newFileSystem(path, null);
Set<String> set = Collections.newSetFromMap(new ConcurrentHashMap<>());
Path profile = system.getPath("install_profile.json");
downloadLibraries(downloader, profile, set);
Path version = system.getPath("version.json");
downloadLibraries(downloader, version, set);
system.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private static void downloadLibraries(SimpleDownloader downloader, Path path, Set<String> set) throws IOException {
JsonArray array = new JsonParser().parse(Files.newBufferedReader(path)).getAsJsonObject().getAsJsonArray("libraries");
for (JsonElement element : array) {
String name = element.getAsJsonObject().get("name").getAsString();
if (!set.add(name)) continue;
String libPath = mavenToPath(name);
JsonObject artifact = element.getAsJsonObject().getAsJsonObject("downloads").getAsJsonObject("artifact");
String url = artifact.get("url").getAsString();
if (url == null || url.trim().isEmpty()) continue;
String hash = artifact.get("sha1").getAsString();
downloader.download(BMCL_API + libPath, "libraries/" + libPath, hash);
}
}
private static String mavenToPath(String maven) {
String type;
if (maven.matches(".*@\\w+$")) {
int i = maven.lastIndexOf('@');
type = maven.substring(i + 1);
maven = maven.substring(0, i);
} else {
type = "jar";
}
String[] arr = maven.split(":");
if (arr.length == 3) {
String pkg = arr[0].replace('.', '/');
return String.format("%s/%s/%s/%s-%s.%s", pkg, arr[1], arr[2], arr[1], arr[2], type);
} else if (arr.length == 4) {
String pkg = arr[0].replace('.', '/');
return String.format("%s/%s/%s/%s-%s-%s.%s", pkg, arr[1], arr[2], arr[1], arr[2], arr[3], type);
} else throw new RuntimeException("Wrong maven coordinate " + maven);
}
}

View File

@ -0,0 +1,13 @@
package io.izzel.arclight.forgeinstaller;
public class InstallInfo {
public Installer installer;
public String[] libraries;
public static class Installer {
public String minecraft;
public String forge;
}
}

View File

@ -0,0 +1,152 @@
package io.izzel.arclight.forgeinstaller;
import java.io.FileNotFoundException;
import java.io.InputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.SocketTimeoutException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.security.MessageDigest;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
public class SimpleDownloader {
private static final String[] MAVEN_REPO = {
"https://repo.spongepowered.org/maven",
"https://oss.sonatype.org/content/repositories/snapshots/",
"https://hub.spigotmc.org/nexus/content/repositories/snapshots/",
"https://bmclapi2.bangbang93.com/maven/",
"https://maven.aliyun.com/repository/public/"
};
private final CompletableFuture<Void> future = new CompletableFuture<>();
private final AtomicInteger integer = new AtomicInteger(0);
private final ExecutorService service = Executors.newFixedThreadPool(8);
private final AtomicBoolean error = new AtomicBoolean(false);
public void run(Callable<Boolean> callable) {
integer.incrementAndGet();
CompletableFuture.supplyAsync(() -> {
try {
return callable.call();
} catch (Throwable e) {
System.err.println(e.toString());
error.compareAndSet(false, true);
return false;
}
}, service).thenAccept(b -> {
int remain = integer.decrementAndGet();
if (remain == 0) {
if (error.get())
future.completeExceptionally(new Exception());
else
future.complete(null);
}
});
}
public void download(String url, String dist, String hash) {
download(url, dist, hash, path -> {});
}
public void download(String url, String dist, String hash, Consumer<Path> onComplete) {
run(() -> downloadFile(url, dist, hash, 5, onComplete));
}
public void downloadMaven(String path) {
run(() -> {
for (String s : MAVEN_REPO) {
if (downloadFile(s + path, "libraries/" + path, null, 3, p -> {})) {
return true;
}
}
return false;
});
}
public boolean awaitTermination() {
try {
future.join();
return true;
} catch (Exception e) {
return false;
} finally {
service.shutdownNow();
}
}
private boolean downloadFile(String url, String dist, String hash, int retry, Consumer<Path> onComplete) throws Exception {
if (retry <= 0) return false;
try {
Path path = Paths.get(dist);
if (Files.exists(path) && hash != null) {
String hash1 = hash(path);
if (hash.equals(hash1)) {
onComplete.accept(path);
return true;
} else {
Files.delete(path);
throw new Exception("Checksum failed: expect " + hash + " but found " + hash1);
}
}
if (!Files.exists(path) && path.getParent() != null) {
Files.createDirectories(path.getParent());
}
System.out.println("Downloading " + url);
InputStream stream = redirect(new URL(url));
Files.copy(stream, path, StandardCopyOption.REPLACE_EXISTING);
stream.close();
if (hash != null) {
String hash1 = hash(path);
if (!hash.equals(hash1)) {
Files.delete(path);
throw new Exception("Checksum failed: expect " + hash + " but found " + hash1);
}
}
onComplete.accept(path);
return true;
} catch (FileNotFoundException | SocketTimeoutException e) {
return false;
} catch (Exception e) {
run(() -> downloadFile(url, dist, hash, retry - 1, onComplete));
System.err.println("Failed to download file " + dist);
throw e;
}
}
private static InputStream redirect(URL url) throws Exception {
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setInstanceFollowRedirects(false);
connection.setReadTimeout(15000);
connection.setConnectTimeout(15000);
switch (connection.getResponseCode()) {
case HttpURLConnection.HTTP_MOVED_PERM:
case HttpURLConnection.HTTP_MOVED_TEMP:
String location = URLDecoder.decode(connection.getHeaderField("Location"), "UTF-8");
return redirect(new URL(url, location));
case HttpURLConnection.HTTP_FORBIDDEN:
case HttpURLConnection.HTTP_NOT_FOUND:
throw new FileNotFoundException();
}
return connection.getInputStream();
}
private static String SHA_PAD = String.format("%040d", 0);
private static String hash(Path path) throws Exception {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
String hash = new BigInteger(1, digest.digest(Files.readAllBytes(path))).toString(16);
return (SHA_PAD + hash).substring(hash.length());
}
}

View File

@ -2,4 +2,7 @@ rootProject.name = 'arclight'
include 'arclight-coremod'
include 'scripts'
include 'arclight-testplugin'
include 'arclight-common'
include 'forge-installer'
include 'arclight-api'