From 2c329d11cfa0ce4a7024bbb870a4a7d6f92498a0 Mon Sep 17 00:00:00 2001 From: FalsePattern Date: Thu, 13 Nov 2025 06:29:03 +0100 Subject: [PATCH] feat: Shadowable deploader --- build.gradle.kts | 82 ++ .../com/falsepattern/deploader/Bootstrap.java | 56 + .../com/falsepattern/deploader}/DepRoot.java | 2 +- .../deploader/DependencyLoaderImpl.java | 1129 ++++++++++++++++ .../falsepattern/deploader}/EarlyConfig.java | 7 +- .../com/falsepattern/deploader}/Internet.java | 8 +- .../deploader}/LetsEncryptHelper.java | 18 +- .../com/falsepattern/deploader/Library.java | 155 +++ .../deploader}/LowLevelCallMultiplexer.java | 2 +- .../com/falsepattern/deploader}/PreShare.java | 2 +- .../com/falsepattern/deploader/Share.java} | 13 +- .../falsepattern/deploader/package-info.java | 26 + .../deploader/version/ComplexVersion.java | 78 ++ .../deploader/version/RawVersion.java | 51 + .../deploader/version/SemanticVersion.java | 180 +++ .../deploader/version/Version.java | 49 + .../deploader/version/package-info.java | 26 + .../java/com/falsepattern/deploader/Stub.java | 235 ++++ .../lib/dependencies/ComplexVersion.java | 11 + .../com/falsepattern/lib/internal/Share.java | 8 - .../lib/internal/asm/CoreLoadingPlugin.java | 11 +- .../lib/internal/asm/RFBLoadingPlugin.java | 14 +- .../dependencies/DependencyLoaderImpl.java | 1141 +---------------- 23 files changed, 2169 insertions(+), 1135 deletions(-) create mode 100644 src/deploader/java/com/falsepattern/deploader/Bootstrap.java rename src/{main/java/com/falsepattern/lib/internal/impl/dependencies => deploader/java/com/falsepattern/deploader}/DepRoot.java (97%) create mode 100644 src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java rename src/{main/java/com/falsepattern/lib/internal/config => deploader/java/com/falsepattern/deploader}/EarlyConfig.java (92%) rename src/{main/java/com/falsepattern/lib/internal => deploader/java/com/falsepattern/deploader}/Internet.java (94%) rename src/{main/java/com/falsepattern/lib/internal/impl/dependencies => deploader/java/com/falsepattern/deploader}/LetsEncryptHelper.java (82%) create mode 100644 src/deploader/java/com/falsepattern/deploader/Library.java rename src/{main/java/com/falsepattern/lib/internal/core => deploader/java/com/falsepattern/deploader}/LowLevelCallMultiplexer.java (98%) rename src/{main/java/com/falsepattern/lib/internal/asm => deploader/java/com/falsepattern/deploader}/PreShare.java (97%) rename src/{main/java/com/falsepattern/lib/internal/impl/dependencies/Pair.java => deploader/java/com/falsepattern/deploader/Share.java} (81%) create mode 100644 src/deploader/java/com/falsepattern/deploader/package-info.java create mode 100644 src/deploader/java/com/falsepattern/deploader/version/ComplexVersion.java create mode 100644 src/deploader/java/com/falsepattern/deploader/version/RawVersion.java create mode 100644 src/deploader/java/com/falsepattern/deploader/version/SemanticVersion.java create mode 100644 src/deploader/java/com/falsepattern/deploader/version/Version.java create mode 100644 src/deploader/java/com/falsepattern/deploader/version/package-info.java create mode 100644 src/deploaderStub/java/com/falsepattern/deploader/Stub.java diff --git a/build.gradle.kts b/build.gradle.kts index 405f207..96ae812 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -47,6 +47,88 @@ minecraft_fp { } } +val java8Compiler = javaToolchains.compilerFor { + languageVersion = JavaLanguageVersion.of(8) + vendor = JvmVendorSpec.ADOPTIUM +} + +fun createSourceSet(name: String, shareDeps: Boolean): SourceSet { + val set = sourceSets.create(name) { + if (shareDeps) { + compileClasspath += sourceSets["patchedMc"].output + } + } + tasks.named(set.compileJavaTaskName).configure { + javaCompiler = java8Compiler + } + afterEvaluate { + tasks.named(set.compileJavaTaskName).configure { + this.sourceCompatibility = "8" + this.targetCompatibility = "8" + this.options.release = null + } + } + if (shareDeps) { + + configurations.named(set.compileClasspathConfigurationName) { + extendsFrom(configurations.getByName("compileClasspath")) + exclude("com.falsepattern", "falsepatternlib-mc1.7.10") + } + configurations.named(set.runtimeClasspathConfigurationName) { + extendsFrom(configurations.getByName("runtimeClasspath")) + exclude("com.falsepattern", "falsepatternlib-mc1.7.10") + } + configurations.named(set.annotationProcessorConfigurationName) { + extendsFrom(configurations.getByName("annotationProcessor")) + exclude("com.falsepattern", "falsepatternlib-mc1.7.10") + } + } + + return set +} + +val depLoader = createSourceSet("deploader", true) + +val depLoaderJar = tasks.register(depLoader.jarTaskName) { + from(depLoader.output) + archiveBaseName = "falsepatternlib-mc1.7.10" + archiveVersion = minecraft_fp.mod.version + archiveClassifier = "deploader" + manifest { + attributes("FPLib-Deploader-Version" to "1") + } +} + +val depLoaderStub = createSourceSet("deploaderStub", true) + +val depLoaderStubJar = tasks.register(depLoaderStub.jarTaskName) { + from(depLoaderStub.output) + archiveBaseName = "falsepatternlib-mc1.7.10" + archiveVersion = minecraft_fp.mod.version + archiveClassifier = "deploader_stub" +} + +sourceSets["main"].compileClasspath += depLoader.output +sourceSets["main"].compileClasspath += depLoaderStub.output + +afterEvaluate { + for (outgoingConfig in listOf("runtimeElements", "apiElements")) { + val outgoing = configurations.getByName(outgoingConfig) + outgoing.outgoing.artifact(depLoaderStubJar) + outgoing.outgoing.artifact(depLoaderJar) + } +} + +tasks.jar { + dependsOn(depLoaderJar, depLoaderStubJar) + from(depLoaderJar.map { it.archiveFile }) { + rename { "fplib_deploader.jar" } + } + from(zipTree(depLoaderStubJar.map { it.archiveFile })) { + exclude("META-INF/MANIFEST.MF") + } +} + repositories { exclusive(maven("horizon", "https://mvn.falsepattern.com/horizon/"), "com.gtnewhorizons.retrofuturabootstrap") exclusive(horizon()) { diff --git a/src/deploader/java/com/falsepattern/deploader/Bootstrap.java b/src/deploader/java/com/falsepattern/deploader/Bootstrap.java new file mode 100644 index 0000000..f7a2bdb --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/Bootstrap.java @@ -0,0 +1,56 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package com.falsepattern.deploader; + +import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; +import lombok.val; + +import net.minecraft.launchwrapper.Launch; +import cpw.mods.fml.relauncher.FMLLaunchHandler; +import cpw.mods.fml.relauncher.Side; + +import java.net.URLClassLoader; +import java.nio.file.Path; + +public class Bootstrap { + public static Path MINECRAFT_HOME_PATH; + + public static void bootstrap(boolean rfb, Path homePath) { + MINECRAFT_HOME_PATH = homePath; + if (rfb) { + val loader = RfbApiImpl.INSTANCE.launchClassLoader(); + PreShare.initDevState(((URLClassLoader)loader).findResource("net/minecraft/world/World.class") != null); + PreShare.initClientState(((URLClassLoader)loader).findResource("net/minecraft/client/Minecraft.class") != null || + ((URLClassLoader)loader).findResource("bao.class") != null); + LowLevelCallMultiplexer.rfbDetected(); + } else { + PreShare.initDevState(Launch.classLoader.findResource("net/minecraft/world/World.class") != null); + PreShare.initClientState(FMLLaunchHandler.side() == Side.CLIENT); + } + LetsEncryptHelper.replaceSSLContext(); + } + + public static void runDepLoader() { + DependencyLoaderImpl.executeDependencyLoading(); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DepRoot.java b/src/deploader/java/com/falsepattern/deploader/DepRoot.java similarity index 97% rename from src/main/java/com/falsepattern/lib/internal/impl/dependencies/DepRoot.java rename to src/deploader/java/com/falsepattern/deploader/DepRoot.java index a766ab6..6d196cb 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DepRoot.java +++ b/src/deploader/java/com/falsepattern/deploader/DepRoot.java @@ -20,7 +20,7 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.impl.dependencies; +package com.falsepattern.deploader; import com.google.gson.annotations.Expose; import lombok.Data; diff --git a/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java b/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java new file mode 100644 index 0000000..bbf67e6 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/DependencyLoaderImpl.java @@ -0,0 +1,1129 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ +package com.falsepattern.deploader; + +import com.falsepattern.deploader.version.RawVersion; +import com.falsepattern.deploader.version.SemanticVersion; +import com.falsepattern.deploader.version.Version; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonParser; +import io.netty.util.internal.ConcurrentSet; +import lombok.NonNull; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.val; +import lombok.var; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.SystemUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JProgressBar; +import javax.swing.WindowConstants; +import java.awt.Dimension; +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Toolkit; +import java.io.BufferedInputStream; +import java.io.BufferedOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.AtomicMoveNotSupportedException; +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.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicLong; +import java.util.function.Consumer; +import java.util.jar.JarInputStream; +import java.util.regex.Pattern; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import java.util.zip.ZipEntry; + +public class DependencyLoaderImpl { + + /** + * Library checksum formats in a decreasing order of quality. + */ + private static final String[] CHECKSUM_TYPES = new String[]{"sha512", "sha256", "sha1", "md5"}; + private static final Set alreadyScannedSources = new ConcurrentSet<>(); + private static final Map loadedLibraries = new ConcurrentHashMap<>(); + private static final Map loadedLibraryMods = new ConcurrentHashMap<>(); + private static final Set remoteMavenRepositories = new ConcurrentSet<>(); + private static final Set localMavenRepositories = new ConcurrentSet<>(); + static final Logger LOG = LogManager.getLogger("FalsePatternLib DepLoader"); + + private static final AtomicLong counter = new AtomicLong(0); + private static final ExecutorService executor = Executors.newCachedThreadPool(r -> { + val thread = new Thread(r); + thread.setDaemon(true); + thread.setName("Dependency Download Thread " + counter.incrementAndGet()); + return thread; + }); + private static final Path libDir; + private static final Path modsDir; + private static final Path tempDir; + + private static AtomicBoolean modDownloaded = new AtomicBoolean(false); + + private static void ensureExists(Path directory) { + if (!Files.exists(directory)) { + try { + Files.createDirectories(directory); + } catch (IOException e) { + LOG.fatal("Failed to create directory {}", directory); + throw new RuntimeException("Failed to create directory " + directory, e); + } + } + } + + static { + Path homeDir; + String homeDirStr = System.getProperty("minecraft.sharedDataDir"); + if (homeDirStr == null) { + homeDirStr = System.getenv("MINECRAFT_SHARED_DATA_DIR"); + } + Path mcHomeDir = Bootstrap.MINECRAFT_HOME_PATH; + if (homeDirStr == null) { + homeDir = mcHomeDir; + } else { + homeDir = Paths.get(homeDirStr); + } + libDir = homeDir.resolve("falsepattern"); + modsDir = mcHomeDir.resolve(Paths.get("mods", "1.7.10")); + tempDir = homeDir.resolve(Paths.get("logs", "falsepattern_tmp")); + ensureExists(libDir); + ensureExists(modsDir); + ensureExists(tempDir); + val oldLibDir = homeDir.resolve(Paths.get("mods", "falsepattern")); + if (Files.exists(oldLibDir)) { + LOG.info("Migrating old library folder. From: " + + oldLibDir.toAbsolutePath() + + ", To: " + + libDir.toAbsolutePath()); + try (val oldFiles = Files.list(oldLibDir)) { + oldFiles.forEach(file -> { + try { + Files.move(file, + libDir.resolve(oldLibDir.relativize(file)), + StandardCopyOption.REPLACE_EXISTING); + } catch (IOException e) { + LOG.warn("Failed to move file " + file.getFileName() + " to new dir! Deleting instead."); + try { + Files.deleteIfExists(file); + } catch (IOException ex) { + LOG.warn("Failed to delete file " + file + "!", ex); + } + } + }); + } catch (IOException e) { + LOG.warn("Failed to iterate old library directory!", e); + } + try { + Files.deleteIfExists(oldLibDir); + } catch (IOException e) { + LOG.warn("Failed to delete old library directory!", e); + } + } + var oldCacheFile = libDir.resolve(".depscan_cache"); + if (Files.exists(oldCacheFile)) { + try { + Files.delete(oldCacheFile); + } catch (IOException e) { + LOG.warn("Failed to delete old depscan cache file!", e); + } + } + } + + public static void addMavenRepo(String url) { + remoteMavenRepositories.add(url); + } + + private static String bytesToHex(byte[] hash) { + StringBuilder hexString = new StringBuilder(2 * hash.length); + for (byte b : hash) { + String hex = Integer.toHexString(0xff & b); + if (hex.length() == 1) { + hexString.append('0'); + } + hexString.append(hex); + } + return hexString.toString(); + } + + @SneakyThrows + private static String digest(String algo, byte[] data) { + return bytesToHex(MessageDigest.getInstance(algo).digest(data)); + } + + @SneakyThrows + private static String hash(String algo, Path file) { + byte[] data = Files.readAllBytes(file); + switch (algo) { + case "md5": + algo = "MD5"; + break; + case "sha1": + algo = "SHA-1"; + break; + case "sha256": + algo = "SHA-256"; + break; + case "sha512": + algo = "SHA-512"; + break; + } + return digest(algo, data); + } + + private static void checkedDelete(Path file) { + try { + Files.delete(file); + } catch (IOException e) { + LOG.fatal("Failed to delete file {}", file); + throw new RuntimeException("Failed to delete file " + file, e); + } + } + + private static synchronized void addToClasspath(URL file) { + try { + LowLevelCallMultiplexer.addURLToClassPath(file); + LOG.debug("Injected file {} into classpath!", file); + } catch (Exception e) { + throw new RuntimeException("Failed to add library to classpath: " + file, e); + } + } + + @SneakyThrows + private static void download(InputStream is, Path target, Consumer downloadSizeCallback) { + if (Files.exists(target)) { + return; + } + Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target)), downloadSizeCallback); + } + + public static void loadLibraries(Library... libraries) { + loadLibrariesAsync(libraries).join(); + } + + public static CompletableFuture loadLibrariesAsync(Library... libraries) { + val futures = new ArrayList>(); + for (val library : libraries) { + val task = new DependencyLoadTask(library.loadingModId, + library.groupId, + library.artifactId, + library.minVersion, + library.maxVersion, + library.preferredVersion, + library.regularSuffix, + library.devSuffix, + false); + futures.add(CompletableFuture.supplyAsync(task::load, executor)); + } + return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(ignored -> { + var tasks = futures.stream().map(it -> it.getNow(null)).filter(Objects::nonNull).collect(Collectors.toSet()); + if (!tasks.isEmpty()) { + var deps = scanDeps(tasks.stream()); + while (deps != null) { + deps = executeArtifactLoading(new ArrayList<>(deps), true); + } + } + }); + } + + private static boolean scanForDepSpecs(URL source, List output, List jijURLs) { + if (!source.getProtocol().equals("file")) { + return false; + } + val fileName = source.getFile(); + Path path; + + try { + path = Paths.get(source.toURI()); + } catch (URISyntaxException e) { + LOG.error("Could not scan URL " + source + " for dependencies", e); + return false; + } + if (alreadyScannedSources.contains(path)) + return true; + alreadyScannedSources.add(path); + boolean found = false; + if (fileName.endsWith(".jar")) { + //Scan jar file for json in META-INF, add them to the list + try (val inputStream = new BufferedInputStream(source.openStream(), 65536); + val jarFile = new JarInputStream(inputStream, false)) { + ZipEntry entry; + while ((entry = jarFile.getNextEntry()) != null) { + val name = entry.getName(); + if (!name.startsWith("META-INF/")) + continue; + if (name.endsWith(".json") && name.matches("META-INF/\\w+.json")) { + try { + output.add(new URL("jar:" + source + "!/" + entry.getName())); + found = true; + } catch (MalformedURLException e) { + LOG.error("Failed to add json source {} to dependency source list: {}", entry.getName(), e); + } + } else if (name.equals("META-INF/falsepatternlib_repo/")) { + try { + jijURLs.add(new URL("jar:" + source + "!/" + entry.getName())); + } catch (MalformedURLException e) { + LOG.error("Failed to add jar-in-jar repo {}: {}", entry.getName(), e); + } + } + } + } catch (IOException e) { + LOG.error("Failed to open jar file {}", source.getPath()); + } + } else { + if (!Files.exists(path) || !Files.isDirectory(path)) { + LOG.warn("Skipping non-directory, nor jar source: {}", source); + return false; + } + //Scan directory for json in META-INF, add them to the list + val metaInf = path.resolve("META-INF"); + if (!Files.exists(metaInf) || !Files.isDirectory(metaInf)) { + return false; + } + try (val files = Files.list(metaInf)) { + found = files.reduce(false, (prev,file) -> { + val entryFileName = file.getFileName().toString(); + if (entryFileName.endsWith(".json")) { + try { + output.add(file.toUri().toURL()); + return true; + } catch (MalformedURLException e) { + LOG.error("Failed to add json source {} to dependency source list: {}", + file.toString(), + e); + } + } else if (entryFileName.equals("falsepatternlib_repo")) { + try { + jijURLs.add(file.toUri().toURL()); + } catch (MalformedURLException e) { + LOG.error("Failed to add jar-in-jar repo {}: {}", file.toString(), e); + } + } + return prev; + }, (a,b) -> a || b); + } catch (IOException e) { + LOG.error("Failed to open directory {}", metaInf); + } + } + return found; + } + + private static Stream grabSourceCandidatesFromFolder(Path folder) { + if (!Files.exists(folder) || !Files.isDirectory(folder)) { + return Stream.empty(); + } + Stream paths; + try (val files = Files.list(folder)) { + //Lazy loading is bad here + paths = files.collect(Collectors.toSet()).stream(); + } catch (IOException ignored) { + return Stream.empty(); + } + + return paths.map(file -> { + try { + return file.toUri().toURL(); + } catch (MalformedURLException e) { + return null; + } + }).filter(Objects::nonNull); + } + + private static final Pattern VERSION_PATTERN = + Pattern.compile("(0|[1-9]\\d*)" + + "(?:\\.(0|[1-9]\\d*))?" + + "(?:\\.(0|[1-9]\\d*))?" + + "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + + "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); + + private enum DependencySide { + COMMON, + CLIENT, + SERVER + } + + private enum DependencyScope { + ALWAYS, + DEV, + OBF + } + + @RequiredArgsConstructor + private static class ScopeSide { + final DependencyScope scope; + final DependencySide side; + + boolean contains(ScopeSide dependency) { + return (dependency.scope == scope || dependency.scope == DependencyScope.ALWAYS) && + (dependency.side == side || dependency.side == DependencySide.COMMON); + } + } + + private static boolean initialScan = false; + private static final List baseTasks = new ArrayList<>(); + + public static void executeDependencyLoading() { + if (!initialScan) { + initialScan = true; + val tasks = scanDeps(); + if (tasks != null) { + baseTasks.addAll(tasks); + } + } + + var tasks = executeArtifactLoading(baseTasks, false); + while (tasks != null) { + baseTasks.addAll(tasks); + tasks = executeArtifactLoading(baseTasks, false); + } + if (modDownloaded.get()) { + if (!SystemUtils.IS_OS_MAC) { + try { + JOptionPane.showMessageDialog(null, "A minecraft mod has been downloaded by the FalsePatternLib dependency downloader.\nYou must restart the game to apply these changes.", "Mod Dependency Download notice", JOptionPane.WARNING_MESSAGE); + } catch (Exception ignored) {} + } + throw new ModDependencyDownloaded("A minecraft mod has been downloaded by the FalsePatternLib dependency downloader, and requires a game restart to get installed properly."); + } + } + + private static class ModDependencyDownloaded extends Error { + public ModDependencyDownloaded(String message) { + super(message); + } + } + + private static @Nullable Set scanDeps() { + LOG.debug("Discovering dependency source candidates..."); + val modsDir = Bootstrap.MINECRAFT_HOME_PATH.resolve("mods"); + val mods1710Dir = modsDir.resolve("1.7.10"); + return scanDeps(Stream.of(LowLevelCallMultiplexer.getClassPathSources().stream(), + grabSourceCandidatesFromFolder(modsDir), + grabSourceCandidatesFromFolder(mods1710Dir)) + .flatMap(i -> i)); + } + + private static @Nullable Set scanDeps(Stream candidatesUnfiltered) { + long start = System.currentTimeMillis(); + val urlsWithoutDeps = new HashSet(); + val depCache = tempDir.resolve(".depscan_cache"); + if (Files.exists(depCache)) { + try { + urlsWithoutDeps.addAll(Files.readAllLines(depCache)); + } catch (IOException e) { + LOG.error("Could not read dependency scanner cache", e); + } + } + val candidates = candidatesUnfiltered + .filter(Objects::nonNull) + .filter((url) -> !urlsWithoutDeps.contains(url.toString())) + .collect(Collectors.toList()); + val urls = new ArrayList(); + val jijURLs = new ArrayList(); + for (val candidate : candidates) { + if (!scanForDepSpecs(candidate, urls, jijURLs)) { + urlsWithoutDeps.add(candidate.toString()); + } + } + try (val out = Files.newBufferedWriter(depCache)) { + for (val noDep : urlsWithoutDeps) { + out.append(noDep).append(System.lineSeparator()); + } + } catch (IOException e) { + LOG.error("Could not write dependency scanner cache", e); + } + val javaVersion = LowLevelCallMultiplexer.javaMajorVersion(); + val dependencySpecs = urls.stream().map((source) -> { + //Convert source to GSON json + try (val is = new BufferedInputStream(source.openStream())) { + val jsonRaw = new JsonParser().parse(new InputStreamReader(is)); + if (!jsonRaw.isJsonObject()) { + return null; + } + val json = jsonRaw.getAsJsonObject(); + if (!(json.has("identifier") && json.get("identifier") + .getAsString() + .equals("falsepatternlib_dependencies"))) { + return null; + } + val builder = new GsonBuilder(); + builder.excludeFieldsWithoutExposeAnnotation(); + val gson = builder.create(); + json.remove("identifier"); + val root = gson.fromJson(json, DepRoot.class); + val minJ = root.minJava(); + val maxJ = root.maxJava(); + if (minJ != null && minJ > javaVersion || maxJ != null && maxJ < javaVersion) { + return null; + } + root.source(source.toString()); + return root; + } catch (Exception e) { + LOG.error("Failed to read json from source {}: {}", source, e); + return null; + } + }).filter(Objects::nonNull).collect(Collectors.toSet()); + long end = System.currentTimeMillis(); + LOG.debug("Discovered {} dependency source candidates in {}ms", dependencySpecs.size(), end - start); + remoteMavenRepositories.addAll(dependencySpecs.stream() + .flatMap((dep) -> dep.repositories().stream()) + .map(repo -> repo.endsWith("/") ? repo : repo + "/") + .collect(Collectors.toSet())); + localMavenRepositories.addAll(jijURLs.stream().map(URL::toString).map(repo -> repo.endsWith("/") ? repo : repo + "/").collect(Collectors.toSet())); + for (DepRoot dependencySpec : dependencySpecs) { + val bundled = dependencySpec.bundledArtifacts(); + if (bundled == null) { + continue; + } + for (String artifact : bundled) { + val parts = artifact.split(":"); + if (parts.length < 3) { + LOG.error("Invalid bundled artifact: {}", dependencySpec); + continue; + } + val groupId = parts[0]; + val artifactId = parts[1]; + Version version = parseVersion(parts[2]); + final String classifier = parts.length > 3 ? parts[3] : null; + if (classifier != null) { + LOG.info("Found bundled artifact: {}:{}:{}:{} from {}", + groupId, + artifactId, + version, + classifier, + dependencySpec.source()); + } else { + LOG.info("Found bundled artifact: {}:{}:{} from {}", + groupId, + artifactId, + version, + dependencySpec.source()); + } + val id = groupId + ":" + artifactId + ":" + classifier; + if (!loadedLibraries.containsKey(id)) { + loadedLibraries.put(id, version); + } + if (!loadedLibraryMods.containsKey(id)) { + loadedLibraryMods.put(id, dependencySpec.source()); + } + } + } + val artifacts = dependencySpecs.stream() + .flatMap(it -> Stream.of( + new SourcedDep(it.source(), it.dependencies(), false), + new SourcedDep(it.source(), it.modDependencies(), true))) + .filter(it -> it.dep != null) + .flatMap(it -> Stream.of( + new ScopedDep(it.source, it.mod, DependencyScope.ALWAYS, it.dep.always()), + new ScopedDep(it.source, it.mod, DependencyScope.DEV, it.dep.dev()), + new ScopedDep(it.source, it.mod, DependencyScope.OBF, it.dep.obf()))) + .flatMap(it -> Stream.concat(it.deps.common().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.COMMON), dep)), + Stream.concat( + it.deps.client().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.CLIENT), dep)), + it.deps.server().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.SERVER), dep)) + ))) + .map((scopedSidedDep) -> { + val source = scopedSidedDep.source; + val scope = scopedSidedDep.scope; + val dep = scopedSidedDep.dep; + val parts = dep.split(":"); + if (parts.length < 3) { + LOG.error("Invalid dependency: {}", dep); + return null; + } + val groupId = parts[0]; + val artifactId = parts[1]; + Version version = parseVersion(parts[2]); + final String classifier = parts.length > 3 ? parts[3] : null; + if (classifier != null) { + LOG.info("Found dependency: {}:{}:{}:{} from {}", + groupId, + artifactId, + version, + classifier, + source); + } else { + LOG.info("Found dependency: {}:{}:{} from {}", + groupId, + artifactId, + version, + source); + } + return new ScopedSidedTask(scope, + new DependencyLoadTask(source, + groupId, + artifactId, + version, + null, + version, + classifier, + classifier, + scopedSidedDep.mod)); + }) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + if (artifacts.isEmpty()) { + return null; + } + return artifacts; + } + + public static Version parseVersion(String versionString) { + try { + val matcher = VERSION_PATTERN.matcher(versionString); + if (!matcher.matches()) { + return new RawVersion(versionString); + } + val major = Integer.parseInt(matcher.group(1)); + val minor = matcher.group(2) == null ? -1 + : Integer.parseInt(matcher.group(2)); + val patch = matcher.group(3) == null ? -1 + : Integer.parseInt(matcher.group(3)); + val preRelease = matcher.group(4); + val build = matcher.group(5); + return new SemanticVersion(major, minor, patch, preRelease, build); + } catch (Exception e) { + return new RawVersion(versionString); + } + } + + private static class SideAwareAssistant { + static ScopeSide current() { + return new ScopeSide(Share.DEV_ENV ? DependencyScope.DEV : DependencyScope.OBF, Share.CLIENT ? DependencySide.CLIENT : DependencySide.SERVER); + } + } + + private static @Nullable Set executeArtifactLoading(List tasks, boolean silent) { + val scopeSide = SideAwareAssistant.current(); + val iter = tasks.iterator(); + val artifactMap = new HashMap(); + while (iter.hasNext()) { + val artifact = iter.next(); + val artifactSide = artifact.scopeSide; + if (!scopeSide.contains(artifactSide)) + continue; + iter.remove(); + val artifactPayload = artifact.task; + val id = artifactPayload.getGroupArtifact(); + if (artifactMap.containsKey(id)) { + val otherArtifact = artifactMap.get(id); + //TODO: Check for conflicts + if (artifactPayload.preferredVersion.compareTo(otherArtifact.preferredVersion) > 0) { + LOG.info("Replacing dependency {}:{} from {} with version {} from {}", + otherArtifact.getGroupArtifact(), + otherArtifact.preferredVersion, + otherArtifact.loadingModId, + artifactPayload.preferredVersion, + artifactPayload.loadingModId); + artifactMap.put(id, artifactPayload); + } + } else { + artifactMap.put(id, artifactPayload); + } + } + if (artifactMap.isEmpty()) + return null; + + val futures = new ArrayList>(); + if (!silent) { + LOG.info("-----------------------------------------------------------"); + LOG.info("FalsePatternLib is downloading dependencies. Please wait..."); + LOG.info("-----------------------------------------------------------"); + } + JFrame theFrame = null; + val progresses = new HashMap(); + if (silent) { + + } else if (SystemUtils.IS_OS_MAC) { + LOG.info("MacOS detected, not creating progress window (your OS is buggy)"); + } else { + try { + val jFrame = new JFrame("Dependency Download"); + val constraints = new GridBagConstraints(); + jFrame.getContentPane().setLayout(new GridBagLayout()); + constraints.gridy = 0; + constraints.gridwidth = 2; + jFrame.add(new JLabel("FalsePatternLib is downloading dependencies, please wait!"), constraints); + constraints.gridwidth = 1; + for (val artifact : artifactMap.entrySet()) { + constraints.gridy++; + jFrame.add(new JLabel(artifact.getKey()), constraints); + val status = new JProgressBar(); + status.setIndeterminate(true); + status.setStringPainted(true); + status.setString("Waiting..."); + progresses.put(artifact.getValue(), status); + jFrame.add(status, constraints); + } + jFrame.pack(); + jFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + jFrame.setLocation(dim.width/2-jFrame.getSize().width/2, dim.height/2-jFrame.getSize().height/2); + theFrame = jFrame; + } catch (Exception ignored) { + } + } + if (theFrame != null) { + for (val task : artifactMap.values()) { + futures.add(CompletableFuture.supplyAsync(() -> { + val bar = progresses.get(task); + bar.setString("Downloading..."); + val res = task.load(); + bar.setMinimum(0); + bar.setMaximum(1); + bar.setValue(1); + bar.setString("Completed!"); + return res; + }, executor)); + } + } else { + for (val task : artifactMap.values()) { + futures.add(CompletableFuture.supplyAsync(task::load, executor)); + } + } + val theFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) + .thenApply(ignored -> + futures.stream() + .map(it -> it.getNow(null)) + .collect(Collectors.toList()) + ); + AtomicBoolean doViz = new AtomicBoolean(true); + if (theFrame != null) { + final var vizThread = getVizThread(doViz, progresses, theFrame); + vizThread.start(); + } + List res; + try { + res = theFuture.join(); + } catch (Error e) { + throw e; + } catch (Throwable t) { + throw new Error(t); + } finally { + if (theFrame != null) { + doViz.set(false); + } + } + return scanDeps(res.stream()); + } + + @NotNull + private static Thread getVizThread(AtomicBoolean doViz, HashMap progresses, JFrame theFrame) { + val vizThread = new Thread(() -> { + int waitBeforeShowing = 100; + while (doViz.get()) { + if (waitBeforeShowing > 0) { + waitBeforeShowing--; + } else if (waitBeforeShowing == 0) { + theFrame.setVisible(true); + waitBeforeShowing = -1; + } + for (val progress : progresses.entrySet()) { + val task = progress.getKey(); + val bar = progress.getValue(); + if (task.contentLength == -1) { + bar.setIndeterminate(true); + } else { + bar.setIndeterminate(false); + bar.setMinimum(0); + bar.setMaximum((int) task.contentLength); + bar.setValue((int) task.downloaded); + } + } + theFrame.repaint(); + try { + Thread.sleep(10); + } catch (InterruptedException ignored) { + } + } + theFrame.dispose(); + }); + vizThread.setDaemon(true); + vizThread.setName("FalsePatternLib Download Visualizer"); + return vizThread; + } + + @RequiredArgsConstructor + private static class SourcedDep { + public final String source; + public final DepRoot.Dependencies dep; + public final boolean mod; + } + + @RequiredArgsConstructor + private static class ScopedDep { + public final String source; + public final boolean mod; + public final DependencyScope scope; + public final DepRoot.SidedDependencies deps; + } + + @RequiredArgsConstructor + private static class ScopedSidedDep { + public final String source; + public final boolean mod; + public final ScopeSide scope; + public final String dep; + } + + @RequiredArgsConstructor + private static class ScopedSidedTask { + public final ScopeSide scopeSide; + public final DependencyLoadTask task; + } + + @RequiredArgsConstructor + private static class DependencyLoadTask { + @NonNull + private final String loadingModId; + @NonNull + private final String groupId; + @NonNull + private final String artifactId; + @NonNull + private final Version minVersion; + private final Version maxVersion; + @NonNull + private final Version preferredVersion; + private final String regularSuffix; + private final String devSuffix; + private final boolean isMod; + + private String suffix; + private String artifactLogName; + private String artifact; + private String mavenJarName; + public volatile String jarName; + private Path file; + + public volatile long contentLength = -1; + public volatile long downloaded = 0; + + private @Nullable URL load() { + setupLibraryNames(); + if (loadedLibraries.containsKey(artifact)) { + alreadyLoaded(); + return null; + } + setupPaths(); + for (val repo: localMavenRepositories) { + val url = tryDownloadFromMaven(repo, true); + if (url != null) { + return url; + } + } + val existingUrl = tryLoadingExistingFile(); + if (existingUrl != null) { + return existingUrl; + } + validateDownloadsAllowed(); + for (var repo : remoteMavenRepositories) { + val url = tryDownloadFromMaven(repo, false); + if (url != null) { + return url; + } + } + throw crashCouldNotDownload(); + } + + private IllegalStateException crashCouldNotDownload() { + val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion + ( + (suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " + loadingModId; + LOG.fatal(errorMessage); + return new IllegalStateException(errorMessage); + } + + private void setupLibraryNames() { + suffix = Share.DEV_ENV ? devSuffix : regularSuffix; + artifactLogName = String.format("%s:%s:%s%s", + groupId, + artifactId, + preferredVersion, + suffix != null ? "-" + suffix : ""); + if (!isMod) { + LOG.info("Adding library {}, requested by mod {}", artifactLogName, loadingModId); + } + artifact = groupId + ":" + artifactId + ":" + suffix; + } + + private void alreadyLoaded() { + val currentVer = loadedLibraries.get(artifact); + if (currentVer.equals(preferredVersion)) { + return; + } + val rangeString = "(minimum: " + minVersion + (maxVersion == null ? "" : ", maximum: " + maxVersion) + ")"; + if (minVersion.compareTo(currentVer) > 0 || (maxVersion != null && maxVersion.compareTo(currentVer) < 0)) { + for (int i = 0; i < 16; i++) { + LOG.fatal("ALERT VVVVVVVVVVVV ALERT"); + } + LOG.fatal("Library {}:{}{} already loaded with version {}, " + + "but a version in the range {} was requested! Thing may go horribly wrong! " + + "Requested by mod: {}, previously loaded by mod: {}", + groupId, + artifactId, + suffix != null ? ":" + suffix : "", + currentVer, + rangeString, + loadingModId, + loadedLibraryMods.get(artifact)); + for (int i = 0; i < 16; i++) { + LOG.fatal("ALERT ^^^^^^^^^^^^ ALERT"); + } + } else { + LOG.info("Attempted loading of library {}:{}{} with preferred version {}, " + + "but version {} was already loaded, which matched the range {}. This is not an " + + "error. " + + "Requested by mod: {}, previously loaded by mod: {}", + groupId, + artifactId, + suffix != null ? ":" + suffix : "", + preferredVersion, + currentVer, + rangeString, + loadingModId, + loadedLibraryMods.get(artifact)); + } + } + + private void setupPaths() { + mavenJarName = + String.format("%s-%s%s.jar", artifactId, preferredVersion, (suffix != null) ? ("-" + suffix) : ""); + jarName = groupId + "-" + mavenJarName; + file = (isMod ? modsDir : libDir).resolve(jarName); + } + + private @Nullable URL tryLoadingExistingFile() { + if (!Files.exists(file)) { + return null; + } + try { + val status = validateChecksum(file); + if (status == ChecksumStatus.FAILED) { + return null; + } else if (status == ChecksumStatus.MISSING) { + LOG.debug("Library {} is missing checksum data! Either it was manually deleted, " + + "or the source repo didn't have it in the first place", artifactLogName); + } + } catch (IOException e) { + LOG.error("Failed to execute validation check for " + artifactLogName, e); + checkedDelete(file); + return null; + } + try { + val theUrl = file.toUri().toURL(); + if (!isMod) { + addToClasspath(theUrl); + } + loadedLibraries.put(artifact, preferredVersion); + LOG.debug("Library {} successfully loaded from disk!", artifactLogName); + return theUrl; + } catch (Exception e) { + LOG.warn("Failed to load library {} from file! Re-downloading...", artifactLogName); + checkedDelete(file); + return null; + } + } + + private void validateDownloadsAllowed() { + if (!EarlyConfig.getInstance().enableLibraryDownloads()) { + val errorMessage = "Failed to load library " + + groupId + + ":" + + artifactId + + ":" + + preferredVersion + + ((suffix != null) ? ":" + suffix : "") + + ": " + + "FalsePatternLib library downloading has been disabled in the config, and the library is not present " + + "on disk! Requested by mod: " + + loadingModId; + LOG.fatal(errorMessage); + throw new IllegalStateException(errorMessage); + } + } + + private static final Object mutex = new Object(); + private @Nullable URL tryDownloadFromMaven(String repo, boolean local) { + synchronized (mutex) { + try { + if (!repo.endsWith("/")) { + repo = repo + "/"; + } + val url = String.format("%s%s/%s/%s/%s", + repo, + groupId.replace('.', '/'), + artifactId, + preferredVersion, + mavenJarName); + String finalRepo = repo; + int retryCount = 0; + while (true) { + retryCount++; + if (retryCount > 3) { + break; + } + val success = new AtomicBoolean(false); + val tmpFile = file.getParent().resolve(file.getFileName().toString() + ".tmp"); + if (Files.exists(tmpFile)) { + Files.delete(tmpFile); + } + Internet.connect(new URL(url), + ex -> LOG.debug("Artifact {} could not be downloaded from repo {}: {}", + artifactLogName, + finalRepo, + ex.getMessage()), + input -> { + LOG.debug("Downloading {} from {}", artifactLogName, finalRepo); + download(input, tmpFile, d -> downloaded += d); + LOG.debug("Downloaded {} from {}", artifactLogName, finalRepo); + success.set(true); + }, + contentLength -> this.contentLength = contentLength); + if (success.get()) { + if (FileUtils.contentEquals(tmpFile.toFile(), file.toFile())) { + Files.delete(tmpFile); + } else { + if (isMod) { + modDownloaded.set(true); + } + try { + Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE); + } catch (AtomicMoveNotSupportedException ignored) { + Files.move(tmpFile, file); + } + } + LOG.debug("Validating checksum for {}", artifactLogName); + val hadChecksum = validateChecksum(url); + switch (hadChecksum) { + case FAILED: + continue; + case OK: + break; + case MISSING: + if (!local) { + LOG.warn("The library {} had no checksum available on the repository.\n" + + "There's a chance it might have gotten corrupted during download,\n" + + "but we're loading it anyways.", artifactLogName); + } + } + loadedLibraries.put(artifact, preferredVersion); + loadedLibraryMods.put(artifact, loadingModId); + val diskUrl = file.toUri().toURL(); + if (!isMod) { + addToClasspath(diskUrl); + } + return diskUrl; + } + } + } catch (IOException ignored) { + } + return null; + } + } + + private ChecksumStatus validateChecksum(String url) throws IOException { + for (val checksumType : CHECKSUM_TYPES) { + val checksumURL = url + "." + checksumType; + val checksumFile = libDir.resolve(jarName + "." + checksumType); + LOG.debug("Attempting to get {} checksum...", checksumType); + val success = new AtomicBoolean(false); + Internet.connect(new URL(checksumURL), + (ex) -> LOG.debug("Could not get {} checksum for {}: {}", + checksumType, + artifactLogName, + ex.getMessage()), + (input) -> { + LOG.debug("Downloading {} checksum for {}", checksumType, artifactLogName); + download(input, checksumFile, d -> {}); + LOG.debug("Downloaded {} checksum for {}", checksumType, artifactLogName); + success.set(true); + }, + length -> {}); + if (success.get()) { + return getChecksumStatus(file, checksumType, checksumFile); + } + } + return ChecksumStatus.MISSING; + } + + private ChecksumStatus validateChecksum(Path file) throws IOException { + for (val checksumType : CHECKSUM_TYPES) { + val checksumFile = libDir.resolve(jarName + "." + checksumType); + LOG.debug("Attempting to read {} checksum from file...", checksumType); + if (Files.exists(checksumFile)) { + return getChecksumStatus(file, checksumType, checksumFile); + } + } + return ChecksumStatus.MISSING; + } + + private ChecksumStatus getChecksumStatus(Path file, String checksumType, Path checksumFile) throws IOException { + val fileHash = hash(checksumType, file); + val referenceHash = new String(Files.readAllBytes(checksumFile)); + if (!fileHash.equals(referenceHash)) { + LOG.error("Failed {} checksum validation for {}.", checksumType, artifactLogName); + checkedDelete(file); + checkedDelete(checksumFile); + return ChecksumStatus.FAILED; + } + LOG.debug("Successfully validated {} checksum for {}.", checksumType, artifactLogName); + return ChecksumStatus.OK; + } + + public String getGroupArtifact() { + return groupId + ":" + artifactId; + } + + private enum ChecksumStatus { + OK, + FAILED, + MISSING + } + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java b/src/deploader/java/com/falsepattern/deploader/EarlyConfig.java similarity index 92% rename from src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java rename to src/deploader/java/com/falsepattern/deploader/EarlyConfig.java index 18726da..e28c6ab 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java +++ b/src/deploader/java/com/falsepattern/deploader/EarlyConfig.java @@ -20,15 +20,14 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.config; +package com.falsepattern.deploader; -import com.falsepattern.lib.internal.Tags; -import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; import com.google.gson.GsonBuilder; import com.google.gson.annotations.Expose; import lombok.Data; import lombok.experimental.Accessors; import lombok.val; +import lombok.var; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.jetbrains.annotations.NotNull; @@ -49,7 +48,7 @@ public class EarlyConfig { private static volatile EarlyConfig instance = null; - private static final Logger LOG = LogManager.getLogger(Tags.MODNAME + " Early Config"); + private static final Logger LOG = LogManager.getLogger("FalsePatternLib Deploader Early Config"); public static @NotNull EarlyConfig getInstance() { val config = instance; diff --git a/src/main/java/com/falsepattern/lib/internal/Internet.java b/src/deploader/java/com/falsepattern/deploader/Internet.java similarity index 94% rename from src/main/java/com/falsepattern/lib/internal/Internet.java rename to src/deploader/java/com/falsepattern/deploader/Internet.java index 8692574..8f689b3 100644 --- a/src/main/java/com/falsepattern/lib/internal/Internet.java +++ b/src/deploader/java/com/falsepattern/deploader/Internet.java @@ -19,9 +19,10 @@ * You should have received a copy of the GNU Lesser General Public License * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal; +package com.falsepattern.deploader; import lombok.val; +import lombok.var; import net.minecraft.launchwrapper.Launch; @@ -69,10 +70,7 @@ public class Internet { connection.setConnectTimeout(3500); connection.setReadTimeout(5000); connection.setRequestProperty("User-Agent", - Tags.MODNAME - + " " - + Tags.VERSION - + " Internet Connector" + "FalsePatternLib DepLoader Internet Connector" + " (https://github.com/FalsePattern/FalsePatternLib)"); for (val header : headers.entrySet()) { val key = header.getKey(); diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java b/src/deploader/java/com/falsepattern/deploader/LetsEncryptHelper.java similarity index 82% rename from src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java rename to src/deploader/java/com/falsepattern/deploader/LetsEncryptHelper.java index a3a840e..4497f66 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/LetsEncryptHelper.java +++ b/src/deploader/java/com/falsepattern/deploader/LetsEncryptHelper.java @@ -20,10 +20,8 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.impl.dependencies; +package com.falsepattern.deploader; -import com.falsepattern.lib.internal.FPLog; -import com.falsepattern.lib.internal.config.EarlyConfig; import lombok.val; import javax.net.ssl.SSLContext; @@ -47,14 +45,14 @@ public class LetsEncryptHelper { @SuppressWarnings("java:S6437") public static void replaceSSLContext() { if (!EarlyConfig.getInstance().enableLetsEncryptRoot()) { - FPLog.LOG.info("[LetsEncryptHelper] Disabled by config"); + DependencyLoaderImpl.LOG.info("[LetsEncryptHelper] Disabled by config"); return; } if (patched) { - FPLog.LOG.info("[LetsEncryptHelper] Already patched"); + DependencyLoaderImpl.LOG.info("[LetsEncryptHelper] Already patched"); return; } - FPLog.LOG.info("[LetsEncryptHelper] Starting patcher"); + DependencyLoaderImpl.LOG.info("[LetsEncryptHelper] Starting patcher"); patched = true; try (val x1 = LetsEncryptHelper.class.getResourceAsStream("/letsencrypt/isrgrootx1.pem"); @@ -76,13 +74,13 @@ public class LetsEncryptHelper { val sslContext = SSLContext.getInstance("TLS"); sslContext.init(null, tmf.getTrustManagers(), null); SSLContext.setDefault(sslContext); - FPLog.LOG.info("[LetsEncryptHelper] Added certificates to trust store."); + DependencyLoaderImpl.LOG.info("[LetsEncryptHelper] Added certificates to trust store."); } catch (IOException e) { - FPLog.LOG.error("[LetsEncryptHelper] Failed to load certificates from classpath.", e); + DependencyLoaderImpl.LOG.error("[LetsEncryptHelper] Failed to load certificates from classpath.", e); } catch (GeneralSecurityException e) { - FPLog.LOG.error("[LetsEncryptHelper] Failed to load default keystore.", e); + DependencyLoaderImpl.LOG.error("[LetsEncryptHelper] Failed to load default keystore.", e); } catch (Throwable t) { - FPLog.LOG.error("[LetsEncryptHelper] Unknown error", t); + DependencyLoaderImpl.LOG.error("[LetsEncryptHelper] Unknown error", t); throw t; } } diff --git a/src/deploader/java/com/falsepattern/deploader/Library.java b/src/deploader/java/com/falsepattern/deploader/Library.java new file mode 100644 index 0000000..4a5d2e9 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/Library.java @@ -0,0 +1,155 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package com.falsepattern.deploader; + +import com.falsepattern.deploader.version.Version; +import lombok.NonNull; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Nullable; + +/** + * @since 0.10.0 + */ +public class Library { + @NonNull + public final String loadingModId; + @NonNull + public final String groupId; + @NonNull + public final String artifactId; + @NonNull + public final Version minVersion; + @Nullable + public final Version maxVersion; + @NonNull + public final Version preferredVersion; + @Nullable + public final String regularSuffix; + @Nullable + public final String devSuffix; + + public Library(@NonNull String loadingModId, @NonNull String groupId, @NonNull String artifactId, @NonNull Version minVersion, Version maxVersion, @NonNull Version preferredVersion, String regularSuffix, String devSuffix) { + this.loadingModId = loadingModId; + this.groupId = groupId; + this.artifactId = artifactId; + this.minVersion = minVersion; + this.maxVersion = maxVersion; + this.preferredVersion = preferredVersion; + this.regularSuffix = regularSuffix; + this.devSuffix = devSuffix; + } + + public static LibraryBuilder builder() { + return new LibraryBuilder(); + } + + /** + * @since 0.10.0 + */ + public static class LibraryBuilder { + private String loadingModId; + private String groupId; + private String artifactId; + private Version minVersion; + private Version maxVersion; + private Version preferredVersion; + private String regularSuffix; + private String devSuffix; + + @ApiStatus.Internal + LibraryBuilder() { + } + + public LibraryBuilder loadingModId(@NonNull String loadingModId) { + this.loadingModId = loadingModId; + return this; + } + + public LibraryBuilder groupId(@NonNull String groupId) { + this.groupId = groupId; + return this; + } + + public LibraryBuilder artifactId(@NonNull String artifactId) { + this.artifactId = artifactId; + return this; + } + + public LibraryBuilder minVersion(@NonNull Version minVersion) { + this.minVersion = minVersion; + return this; + } + + public LibraryBuilder maxVersion(Version maxVersion) { + this.maxVersion = maxVersion; + return this; + } + + public LibraryBuilder preferredVersion(@NonNull Version preferredVersion) { + this.preferredVersion = preferredVersion; + return this; + } + + public LibraryBuilder regularSuffix(String regularSuffix) { + this.regularSuffix = regularSuffix; + return this; + } + + public LibraryBuilder devSuffix(String devSuffix) { + this.devSuffix = devSuffix; + return this; + } + + public Library build() { + return new Library(loadingModId, + groupId, + artifactId, + minVersion, + maxVersion, + preferredVersion, + regularSuffix, + devSuffix); + } + + @Override + public String toString() { + return "Library.LibraryBuilder(loadingModId=" + + this.loadingModId + + ", groupId=" + + this.groupId + + ", artifactId=" + + this.artifactId + + ", minVersion=" + + this.minVersion + + ", maxVersion=" + + this.maxVersion + + ", preferredVersion=" + + this.preferredVersion + + ", regularSuffix=" + + this.regularSuffix + + ", devSuffix=" + + this.devSuffix + + ")"; + } + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java b/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java similarity index 98% rename from src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java rename to src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java index 6140e2b..9fc78b1 100644 --- a/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java +++ b/src/deploader/java/com/falsepattern/deploader/LowLevelCallMultiplexer.java @@ -20,7 +20,7 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.core; +package com.falsepattern.deploader; import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; import lombok.experimental.UtilityClass; diff --git a/src/main/java/com/falsepattern/lib/internal/asm/PreShare.java b/src/deploader/java/com/falsepattern/deploader/PreShare.java similarity index 97% rename from src/main/java/com/falsepattern/lib/internal/asm/PreShare.java rename to src/deploader/java/com/falsepattern/deploader/PreShare.java index 0109aac..a66b81b 100644 --- a/src/main/java/com/falsepattern/lib/internal/asm/PreShare.java +++ b/src/deploader/java/com/falsepattern/deploader/PreShare.java @@ -20,7 +20,7 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.asm; +package com.falsepattern.deploader; public class PreShare { private static volatile int devEnv = -1; diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/Pair.java b/src/deploader/java/com/falsepattern/deploader/Share.java similarity index 81% rename from src/main/java/com/falsepattern/lib/internal/impl/dependencies/Pair.java rename to src/deploader/java/com/falsepattern/deploader/Share.java index b59cf23..97b0be6 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/Pair.java +++ b/src/deploader/java/com/falsepattern/deploader/Share.java @@ -20,14 +20,9 @@ * along with FalsePatternLib. If not, see . */ -package com.falsepattern.lib.internal.impl.dependencies; +package com.falsepattern.deploader; -public class Pair { - public final A a; - public final B b; - - public Pair(A a, B b) { - this.a = a; - this.b = b; - } +public class Share { + public static final boolean DEV_ENV = PreShare.devEnv(); + public static final boolean CLIENT = PreShare.client(); } diff --git a/src/deploader/java/com/falsepattern/deploader/package-info.java b/src/deploader/java/com/falsepattern/deploader/package-info.java new file mode 100644 index 0000000..09dd793 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/package-info.java @@ -0,0 +1,26 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +@ApiStatus.Internal +package com.falsepattern.deploader; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/deploader/java/com/falsepattern/deploader/version/ComplexVersion.java b/src/deploader/java/com/falsepattern/deploader/version/ComplexVersion.java new file mode 100644 index 0000000..9cc8782 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/version/ComplexVersion.java @@ -0,0 +1,78 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ +package com.falsepattern.deploader.version; + +import lombok.val; +import org.jetbrains.annotations.NotNull; + +import java.util.Arrays; +import java.util.stream.Collectors; + +public class ComplexVersion extends Version { + public final Version[] versions; + + public ComplexVersion(@NotNull Version mainVersion, Version... subVersions) { + this.versions = new Version[subVersions.length + 1]; + this.versions[0] = mainVersion; + System.arraycopy(subVersions, 0, this.versions, 1, subVersions.length); + } + + public ComplexVersion(Version[] versions) { + this.versions = versions; + } + + @Override + public int compareTo(@NotNull Version o) { + if (o instanceof ComplexVersion) { + val other = (ComplexVersion) o; + int count = Math.min(versions.length, other.versions.length); + for (int i = 0; i < count; i++) { + val result = versions[i].compareTo(other.versions[i]); + if (result != 0) { + return result; + } + } + if (versions.length != other.versions.length) { + return versions.length - other.versions.length; + } else { + return 0; + } + } else if (o instanceof SemanticVersion) { + val other = (SemanticVersion) o; + val result = versions[0].compareTo(other); + if (result != 0) { + return result; + } + if (versions.length > 1) { + return -1; + } + return 0; + } else { + throw new IllegalArgumentException("Could not compare version with class " + o.getClass().getName()); + } + } + + @Override + public String toString() { + return Arrays.stream(versions).map(Version::toString).collect(Collectors.joining("-")); + } +} diff --git a/src/deploader/java/com/falsepattern/deploader/version/RawVersion.java b/src/deploader/java/com/falsepattern/deploader/version/RawVersion.java new file mode 100644 index 0000000..0c7f97d --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/version/RawVersion.java @@ -0,0 +1,51 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package com.falsepattern.deploader.version; + +import org.jetbrains.annotations.NotNull; + +public class RawVersion extends Version { + public final String versionString; + + public RawVersion(String versionString) { + this.versionString = versionString; + } + + @Override + public int compareTo(@NotNull Version o) { + if (o instanceof RawVersion) { + return versionString.compareTo(((RawVersion) o).versionString); + } else if (o instanceof SemanticVersion) { + return 1; + } else if (o instanceof ComplexVersion) { + return 1; + } else { + throw new IllegalArgumentException("Unknown version type: " + o.getClass().getName()); + } + } + + @Override + public String toString() { + return versionString; + } +} diff --git a/src/deploader/java/com/falsepattern/deploader/version/SemanticVersion.java b/src/deploader/java/com/falsepattern/deploader/version/SemanticVersion.java new file mode 100644 index 0000000..3ff2c65 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/version/SemanticVersion.java @@ -0,0 +1,180 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ +package com.falsepattern.deploader.version; + +import lombok.Getter; +import lombok.val; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.NotNull; + +import java.util.Objects; + +public class SemanticVersion extends Version { + @Getter + private final int majorVersion; + @Getter + private final int minorVersion; + @Getter + private final int patchVersion; + @Getter + private final String preRelease; + @Getter + private final String build; + + public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, String preRelease, String build) { + this.majorVersion = majorVersion; + this.minorVersion = minorVersion; + this.patchVersion = patchVersion; + preRelease = preRelease == null ? null : preRelease.trim(); + build = build == null ? null : build.trim(); + this.preRelease = "".equals(preRelease) ? null : preRelease; + this.build = "".equals(build) ? null : build; + } + + public SemanticVersion(int majorVersion, int minorVersion, int patchVersion, String preRelease) { + this(majorVersion, minorVersion, patchVersion, preRelease, null); + } + + public SemanticVersion(int majorVersion, int minorVersion, int patchVersion) { + this(majorVersion, minorVersion, patchVersion, null, null); + } + + /** + * @since 0.10.0 + */ + public SemanticVersion(int majorVersion, int minorVersion) { + this(majorVersion, minorVersion, -1, null, null); + } + + + /** + * @since 0.10.0 + */ + public SemanticVersion(int majorVersion) { + this(majorVersion, -1, -1, null, null); + } + + public static SemanticVersionBuilder builder() { + return new SemanticVersionBuilder(); + } + + @Override + public int compareTo(@NotNull Version o) { + if (o instanceof ComplexVersion) { + val result = this.compareTo(((ComplexVersion) o).versions[0]); + if (result != 0) { + return result; + } else if (((ComplexVersion) o).versions.length > 1) { + return 1; + } else { + return 0; + } + } else if (o instanceof SemanticVersion) { + val other = (SemanticVersion) o; + if (majorVersion != other.majorVersion) { + return majorVersion - other.majorVersion; + } else if (minorVersion != other.minorVersion) { + return minorVersion - other.minorVersion; + } else if (patchVersion != other.patchVersion) { + return patchVersion - other.patchVersion; + } else if (!Objects.equals(preRelease, other.preRelease)) { + if (preRelease == null) { + return 1; + } else if (other.preRelease == null) { + return -1; + } + return preRelease.compareTo(other.preRelease); + } else { + return 0; + } + } + return 0; + } + + @Override + public String toString() { + // @formatter:off + return majorVersion + (minorVersion < 0 ? "" : "." + minorVersion) + + (patchVersion < 0 ? "" : "." + patchVersion) + (preRelease == null ? "" : "-" + preRelease) + + (build == null ? "" : "+" + build); + // @formatter:on + } + + /** + * @since 0.10.0 + */ + public static class SemanticVersionBuilder { + private int majorVersion; + private int minorVersion = -1; + private int patchVersion = -1; + private String preRelease; + private String build; + + @ApiStatus.Internal + SemanticVersionBuilder() { + } + + public SemanticVersionBuilder majorVersion(int majorVersion) { + this.majorVersion = majorVersion; + return this; + } + + public SemanticVersionBuilder minorVersion(int minorVersion) { + this.minorVersion = minorVersion; + return this; + } + + public SemanticVersionBuilder patchVersion(int patchVersion) { + this.patchVersion = patchVersion; + return this; + } + + public SemanticVersionBuilder preRelease(String preRelease) { + this.preRelease = preRelease; + return this; + } + + public SemanticVersionBuilder build(String build) { + this.build = build; + return this; + } + + public SemanticVersion build() { + return new SemanticVersion(majorVersion, minorVersion, patchVersion, preRelease, build); + } + + @Override + public String toString() { + return "SemanticVersion.SemanticVersionBuilder(majorVersion=" + + this.majorVersion + + ", minorVersion=" + + this.minorVersion + + ", patchVersion=" + + this.patchVersion + + ", preRelease=" + + this.preRelease + + ", build=" + + this.build + + ")"; + } + } +} diff --git a/src/deploader/java/com/falsepattern/deploader/version/Version.java b/src/deploader/java/com/falsepattern/deploader/version/Version.java new file mode 100644 index 0000000..b4fdfc6 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/version/Version.java @@ -0,0 +1,49 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ +package com.falsepattern.deploader.version; + +import com.falsepattern.deploader.DependencyLoaderImpl; + +public abstract class Version implements Comparable { + protected Version() { + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof Version)) { + return false; + } + return equals((Version) obj); + } + + public boolean equals(Version other) { + return compareTo(other) == 0; + } + + /** + * + * @since 1.7.0 + */ + public static Version parse(String versionString) { + return DependencyLoaderImpl.parseVersion(versionString); + } +} diff --git a/src/deploader/java/com/falsepattern/deploader/version/package-info.java b/src/deploader/java/com/falsepattern/deploader/version/package-info.java new file mode 100644 index 0000000..186d9d1 --- /dev/null +++ b/src/deploader/java/com/falsepattern/deploader/version/package-info.java @@ -0,0 +1,26 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +@ApiStatus.Internal +package com.falsepattern.deploader.version; + +import org.jetbrains.annotations.ApiStatus; \ No newline at end of file diff --git a/src/deploaderStub/java/com/falsepattern/deploader/Stub.java b/src/deploaderStub/java/com/falsepattern/deploader/Stub.java new file mode 100644 index 0000000..805f071 --- /dev/null +++ b/src/deploaderStub/java/com/falsepattern/deploader/Stub.java @@ -0,0 +1,235 @@ +/* + * This file is part of FalsePatternLib. + * + * Copyright (C) 2022-2025 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * FalsePatternLib is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, only version 3 of the License. + * + * FalsePatternLib is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with FalsePatternLib. If not, see . + */ + +package com.falsepattern.deploader; + +import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; +import com.gtnewhorizons.retrofuturabootstrap.api.ExtensibleClassLoader; +import org.apache.commons.io.IOUtils; + +import net.minecraft.launchwrapper.Launch; + +import java.io.File; +import java.io.IOException; +import java.io.OutputStream; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.DirectoryStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.file.StandardOpenOption; +import java.util.jar.JarEntry; +import java.util.jar.JarFile; +import java.util.jar.JarInputStream; + +public final class Stub { + private static final String BLACKBOARD_MARKER = "FPLIB_DEPLOADER"; + private static final String BLACKBOARD_RUN_DEPLOADER_MARKER = "FPLIB_RUN_DEPLOADER"; + private static boolean rfbDetected = false; + private int version = -1; + private Path path = null; + //Call this from the static initializer of your coremod / RFB coremod + public static void bootstrap(boolean rfb) { + if (rfb) { + rfbDetected = true; + RFBUtil.preinit(); + } + Object deploader = Launch.blackboard.get(BLACKBOARD_MARKER); + if (deploader != null) { + return; + } + Stub scanner = new Stub(); + @SuppressWarnings("resource") + URLClassLoader cl = classLoader(); + for (URL url: cl.getURLs()) { + try { + scanner.scanCandidate(new File(url.toURI()).toPath()); + } catch (Exception ignored) { + } + } + Path mcHomeDir = gameDir(); + Path mods = mcHomeDir.resolve("mods"); + Path mods1710 = mods.resolve("1.7.10"); + scanner.scanCandidates(mods); + scanner.scanCandidates(mods1710); + if (scanner.path == null) { + throw new IllegalStateException("Failed to find fplib deploader! Are you sure you bundled it in your jar?"); + } + + Path homeDir; + String homeDirStr = System.getProperty("minecraft.sharedDataDir"); + if (homeDirStr == null) { + homeDirStr = System.getenv("MINECRAFT_SHARED_DATA_DIR"); + } + if (homeDirStr == null) { + homeDir = mcHomeDir; + } else { + homeDir = Paths.get(homeDirStr); + } + Path libDir = homeDir.resolve("falsepattern"); + if (!Files.exists(libDir)) { + try { + Files.createDirectories(libDir); + } catch (IOException e) { + throw new RuntimeException("Failed to create directory " + libDir, e); + } + } + Path deploaderFile = libDir.resolve("fplib_deploader.jar"); + if (Files.exists(deploaderFile)) { + try { + Files.delete(deploaderFile); + } catch (IOException ignored) { + } + } + try (OutputStream out = Files.newOutputStream(deploaderFile, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); + JarFile jarIn = new JarFile(scanner.path.toFile())) { + JarEntry theEntry = jarIn.getJarEntry("fplib_deploader.jar"); + IOUtils.copy(jarIn.getInputStream(theEntry), out); + } catch (IOException e) { + throw new RuntimeException("Failed to write deploader jar file!", e); + } + + try { + addURLToClassPath(deploaderFile.toUri().toURL()); + } catch (MalformedURLException e) { + throw new RuntimeException("Failed to add deploader to classpath!", e); + } + try { + Class bootstrap = cl.loadClass("com.falsepattern.deploader.Bootstrap"); + Method bootstrapMethod = bootstrap.getDeclaredMethod("bootstrap", boolean.class, Path.class); + bootstrapMethod.invoke(null, rfb, mcHomeDir); + Method runDepLoaderMethod = bootstrap.getDeclaredMethod("runDepLoader"); + Launch.blackboard.put(BLACKBOARD_MARKER, bootstrap); + Launch.blackboard.put(BLACKBOARD_RUN_DEPLOADER_MARKER, runDepLoaderMethod); + } catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Failed to load deploader bootstrap class!", e); + } + } + + private void scanCandidates(Path dir) { + if (!Files.exists(dir) || !Files.isDirectory(dir)) + return; + try (DirectoryStream stream = Files.newDirectoryStream(dir)) { + stream.forEach(this::scanCandidate); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + private void scanCandidate(Path file) { + if (Files.isRegularFile(file) && file.getFileName().toString().endsWith(".jar")) { + try(JarFile jarFile = new JarFile(file.toFile())) { + JarEntry entry = jarFile.getJarEntry("fplib_deploader.jar"); + if (entry != null) { + JarInputStream jarInput = new JarInputStream(jarFile.getInputStream(entry)); + while (jarInput.getManifest() == null) { + jarInput.getNextJarEntry(); + } + String version = jarInput.getManifest().getMainAttributes().getValue("FPLib-Deploader-Version"); + if (version != null) { + int versionInt = Integer.parseInt(version); + if (versionInt > this.version) { + this.version = versionInt; + this.path = file; + } + } + } + } catch (Exception ignored) {} + } + } + + public static void runDepLoader() { + Object runDepLoader = Launch.blackboard.get(BLACKBOARD_RUN_DEPLOADER_MARKER); + if (runDepLoader == null) { + throw new IllegalStateException("Tried to call runDepLoader without calling bootstrap() first!"); + } + try { + ((Method)runDepLoader).invoke(null); + } catch (IllegalAccessException | InvocationTargetException e) { + throw new RuntimeException("Could not run FalsePatternLib deploader!", e); + } + } + + public static void addURLToClassPath(URL url) { + if (rfbDetected) { + RFBUtil.addURLToClassPath(url); + } else { + LaunchWrapperUtil.addURLToClassPath(url); + } + } + + private static URLClassLoader classLoader() { + if (rfbDetected) { + return RFBUtil.classLoader(); + } else { + return LaunchWrapperUtil.classLoader(); + } + } + + public static Path gameDir() { + if (rfbDetected) { + return RFBUtil.gameDir(); + } else { + return LaunchWrapperUtil.gameDir(); + } + } + + private static class RFBUtil { + static void preinit() { + ExtensibleClassLoader loader = RfbApiImpl.INSTANCE.launchClassLoader(); + try { + Method exc = loader.getClass() + .getDeclaredMethod("addClassLoaderExclusion", String.class); + exc.invoke(loader, "com.falsepattern.deploader."); + } catch (Exception ignored) {} + } + + static void addURLToClassPath(URL url) { + RfbApiImpl.INSTANCE.compatClassLoader().addURL(url); + } + + static URLClassLoader classLoader() { + return RfbApiImpl.INSTANCE.compatClassLoader().asURLClassLoader(); + } + + static Path gameDir() { + return RfbApiImpl.INSTANCE.gameDirectory(); + } + } + + private static class LaunchWrapperUtil { + static Path gameDir() { + return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath(); + } + static void addURLToClassPath(URL url) { + Launch.classLoader.addURL(url); + } + + static URLClassLoader classLoader() { + return Launch.classLoader; + } + } +} diff --git a/src/main/java/com/falsepattern/lib/dependencies/ComplexVersion.java b/src/main/java/com/falsepattern/lib/dependencies/ComplexVersion.java index 9def8ee..e29b639 100644 --- a/src/main/java/com/falsepattern/lib/dependencies/ComplexVersion.java +++ b/src/main/java/com/falsepattern/lib/dependencies/ComplexVersion.java @@ -23,6 +23,7 @@ package com.falsepattern.lib.dependencies; import lombok.NonNull; import lombok.val; +import org.jetbrains.annotations.ApiStatus; import java.util.Arrays; import java.util.stream.Collectors; @@ -36,6 +37,16 @@ public class ComplexVersion extends Version { System.arraycopy(subVersions, 0, this.versions, 1, subVersions.length); } + @ApiStatus.Internal + public ComplexVersion(Version[] versions) { + this.versions = versions; + } + + @ApiStatus.Internal + public Version[] versionsRaw() { + return this.versions; + } + @Override public int compareTo(@NonNull Version o) { if (o instanceof ComplexVersion) { diff --git a/src/main/java/com/falsepattern/lib/internal/Share.java b/src/main/java/com/falsepattern/lib/internal/Share.java index abd7715..8b253a4 100644 --- a/src/main/java/com/falsepattern/lib/internal/Share.java +++ b/src/main/java/com/falsepattern/lib/internal/Share.java @@ -22,19 +22,11 @@ package com.falsepattern.lib.internal; -import com.falsepattern.lib.internal.asm.PreShare; import lombok.AccessLevel; import lombok.NoArgsConstructor; -import lombok.val; - -import net.minecraft.launchwrapper.Launch; - -import java.io.IOException; @NoArgsConstructor(access = AccessLevel.PRIVATE) public final class Share { - public static final boolean DEV_ENV = PreShare.devEnv(); - public static final boolean CLIENT = PreShare.client(); public static boolean EARLY_INIT_DONE = false; } diff --git a/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java b/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java index e60cdef..a6c5561 100644 --- a/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java +++ b/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java @@ -21,10 +21,9 @@ */ package com.falsepattern.lib.internal.asm; +import com.falsepattern.deploader.Stub; import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Tags; -import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl; -import com.falsepattern.lib.internal.impl.dependencies.LetsEncryptHelper; import com.falsepattern.lib.internal.logging.CrashImprover; import com.falsepattern.lib.internal.logging.NotEnoughVerbosity; import com.falsepattern.lib.mapping.MappingManager; @@ -36,12 +35,10 @@ import lombok.val; import net.minecraft.launchwrapper.IClassTransformer; import net.minecraft.launchwrapper.Launch; import net.minecraft.launchwrapper.LaunchClassLoader; -import cpw.mods.fml.relauncher.FMLLaunchHandler; import cpw.mods.fml.relauncher.IFMLLoadingPlugin; import cpw.mods.fml.relauncher.IFMLLoadingPlugin.MCVersion; import cpw.mods.fml.relauncher.IFMLLoadingPlugin.Name; import cpw.mods.fml.relauncher.IFMLLoadingPlugin.SortingIndex; -import cpw.mods.fml.relauncher.Side; import java.util.List; import java.util.Map; @@ -58,9 +55,7 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { private static boolean obfuscated; static { - PreShare.initDevState(Launch.classLoader.findResource("net/minecraft/world/World.class") != null); - PreShare.initClientState(FMLLaunchHandler.side() == Side.CLIENT); - LetsEncryptHelper.replaceSSLContext(); + Stub.bootstrap(false); FPLog.LOG.info("Removing skill issues..."); try { Class.forName("thermos.Thermos"); @@ -71,7 +66,7 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { //Scan for dependencies now FPLog.LOG.info("Scanning for deps..."); long start = System.nanoTime(); - DependencyLoaderImpl.executeDependencyLoading(); + Stub.runDepLoader(); long end = System.nanoTime(); FPLog.LOG.info("Scanned in " + (end - start) / 1000000 + "ms"); //Initializing the rest diff --git a/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java b/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java index 704053e..b20b1af 100644 --- a/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java +++ b/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java @@ -22,15 +22,11 @@ package com.falsepattern.lib.internal.asm; -import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; -import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl; -import com.falsepattern.lib.internal.impl.dependencies.LetsEncryptHelper; +import com.falsepattern.deploader.Stub; import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; import com.gtnewhorizons.retrofuturabootstrap.api.RfbPlugin; import lombok.val; -import java.net.URLClassLoader; - public class RFBLoadingPlugin implements RfbPlugin { static { val loader = RfbApiImpl.INSTANCE.launchClassLoader(); @@ -47,11 +43,7 @@ public class RFBLoadingPlugin implements RfbPlugin { exc.invoke(loader, "com.falsepattern.lib.internal.config.EarlyConfig"); exc.invoke(loader, "com.falsepattern.lib.internal.core.LowLevelCallMultiplexer"); } catch (Exception ignored) {} - PreShare.initDevState(((URLClassLoader)loader).findResource("net/minecraft/world/World.class") != null); - PreShare.initClientState(((URLClassLoader)loader).findResource("net/minecraft/client/Minecraft.class") != null || - ((URLClassLoader)loader).findResource("bao.class") != null); - LetsEncryptHelper.replaceSSLContext(); - LowLevelCallMultiplexer.rfbDetected(); - DependencyLoaderImpl.executeDependencyLoading(); + Stub.bootstrap(true); + Stub.runDepLoader(); } } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java index 7e59b62..a9bb8e1 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/dependencies/DependencyLoaderImpl.java @@ -19,1117 +19,104 @@ * You should have received a copy of the GNU Lesser General Public License * along with FalsePatternLib. If not, see . */ + package com.falsepattern.lib.internal.impl.dependencies; +import com.falsepattern.lib.dependencies.ComplexVersion; import com.falsepattern.lib.dependencies.Library; import com.falsepattern.lib.dependencies.SemanticVersion; import com.falsepattern.lib.dependencies.Version; -import com.falsepattern.lib.internal.Internet; -import com.falsepattern.lib.internal.Share; -import com.falsepattern.lib.internal.Tags; -import com.falsepattern.lib.internal.config.EarlyConfig; -import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; -import com.falsepattern.lib.util.FileUtil; -import com.google.gson.GsonBuilder; -import com.google.gson.JsonParser; -import io.netty.util.internal.ConcurrentSet; -import lombok.NonNull; -import lombok.RequiredArgsConstructor; -import lombok.SneakyThrows; import lombok.val; -import org.apache.commons.io.FileUtils; -import org.apache.commons.lang3.SystemUtils; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.Nullable; -import javax.swing.JFrame; -import javax.swing.JLabel; -import javax.swing.JOptionPane; -import javax.swing.JProgressBar; -import javax.swing.WindowConstants; -import java.awt.Dimension; -import java.awt.GridBagConstraints; -import java.awt.GridBagLayout; -import java.awt.Toolkit; -import java.io.BufferedInputStream; -import java.io.BufferedOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.net.MalformedURLException; -import java.net.URISyntaxException; -import java.net.URL; -import java.nio.file.AtomicMoveNotSupportedException; -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.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; import java.util.concurrent.CompletableFuture; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicLong; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.jar.JarInputStream; -import java.util.regex.Pattern; -import java.util.stream.Collectors; -import java.util.stream.Stream; -import java.util.zip.ZipEntry; public class DependencyLoaderImpl { - /** - * Library checksum formats in a decreasing order of quality. - */ - private static final String[] CHECKSUM_TYPES = new String[]{"sha512", "sha256", "sha1", "md5"}; - private static final Set alreadyScannedSources = new ConcurrentSet<>(); - private static final Map loadedLibraries = new ConcurrentHashMap<>(); - private static final Map loadedLibraryMods = new ConcurrentHashMap<>(); - private static final Set remoteMavenRepositories = new ConcurrentSet<>(); - private static final Set localMavenRepositories = new ConcurrentSet<>(); - private static final Logger LOG = LogManager.getLogger(Tags.MODNAME + " Library Loader"); - - private static final AtomicLong counter = new AtomicLong(0); - private static final ExecutorService executor = Executors.newCachedThreadPool(r -> { - val thread = new Thread(r); - thread.setDaemon(true); - thread.setName("Dependency Download Thread " + counter.incrementAndGet()); - return thread; - }); - private static final Path libDir; - private static final Path modsDir; - private static final Path tempDir; - - private static AtomicBoolean modDownloaded = new AtomicBoolean(false); - - private static void ensureExists(Path directory) { - if (!Files.exists(directory)) { - try { - Files.createDirectories(directory); - } catch (IOException e) { - LOG.fatal("Failed to create directory {}", directory); - throw new RuntimeException("Failed to create directory " + directory, e); - } - } - } - - static { - Path homeDir; - String homeDirStr = System.getProperty("minecraft.sharedDataDir"); - if (homeDirStr == null) { - homeDirStr = System.getenv("MINECRAFT_SHARED_DATA_DIR"); - } - Path mcHomeDir = FileUtil.getMinecraftHomePath(); - if (homeDirStr == null) { - homeDir = mcHomeDir; - } else { - homeDir = Paths.get(homeDirStr); - } - libDir = homeDir.resolve("falsepattern"); - modsDir = mcHomeDir.resolve(Paths.get("mods", "1.7.10")); - tempDir = homeDir.resolve(Paths.get("logs", "falsepattern_tmp")); - ensureExists(libDir); - ensureExists(modsDir); - ensureExists(tempDir); - val oldLibDir = homeDir.resolve(Paths.get("mods", "falsepattern")); - if (Files.exists(oldLibDir)) { - LOG.info("Migrating old library folder. From: " - + oldLibDir.toAbsolutePath() - + ", To: " - + libDir.toAbsolutePath()); - try (val oldFiles = Files.list(oldLibDir)) { - oldFiles.forEach(file -> { - try { - Files.move(file, - libDir.resolve(oldLibDir.relativize(file)), - StandardCopyOption.REPLACE_EXISTING); - } catch (IOException e) { - LOG.warn("Failed to move file " + file.getFileName() + " to new dir! Deleting instead."); - try { - Files.deleteIfExists(file); - } catch (IOException ex) { - LOG.warn("Failed to delete file " + file + "!", ex); - } - } - }); - } catch (IOException e) { - LOG.warn("Failed to iterate old library directory!", e); - } - try { - Files.deleteIfExists(oldLibDir); - } catch (IOException e) { - LOG.warn("Failed to delete old library directory!", e); - } - } - var oldCacheFile = libDir.resolve(".depscan_cache"); - if (Files.exists(oldCacheFile)) { - try { - Files.delete(oldCacheFile); - } catch (IOException e) { - LOG.warn("Failed to delete old depscan cache file!", e); - } - } - } - public static void addMavenRepo(String url) { - remoteMavenRepositories.add(url); - } - - private static String bytesToHex(byte[] hash) { - StringBuilder hexString = new StringBuilder(2 * hash.length); - for (byte b : hash) { - String hex = Integer.toHexString(0xff & b); - if (hex.length() == 1) { - hexString.append('0'); - } - hexString.append(hex); - } - return hexString.toString(); - } - - @SneakyThrows - private static String digest(String algo, byte[] data) { - return bytesToHex(MessageDigest.getInstance(algo).digest(data)); - } - - @SneakyThrows - private static String hash(String algo, Path file) { - byte[] data = Files.readAllBytes(file); - switch (algo) { - case "md5": - algo = "MD5"; - break; - case "sha1": - algo = "SHA-1"; - break; - case "sha256": - algo = "SHA-256"; - break; - case "sha512": - algo = "SHA-512"; - break; - } - return digest(algo, data); - } - - private static void checkedDelete(Path file) { - try { - Files.delete(file); - } catch (IOException e) { - LOG.fatal("Failed to delete file {}", file); - throw new RuntimeException("Failed to delete file " + file, e); - } - } - - private static synchronized void addToClasspath(URL file) { - try { - LowLevelCallMultiplexer.addURLToClassPath(file); - LOG.debug("Injected file {} into classpath!", file); - } catch (Exception e) { - throw new RuntimeException("Failed to add library to classpath: " + file, e); - } - } - - @SneakyThrows - private static void download(InputStream is, Path target, Consumer downloadSizeCallback) { - if (Files.exists(target)) { - return; - } - Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target)), downloadSizeCallback); - } - - public static void loadLibraries(Library... libraries) { - loadLibrariesAsync(libraries).join(); + com.falsepattern.deploader.DependencyLoaderImpl.addMavenRepo(url); } + /** + * @since 0.10.0 + */ public static CompletableFuture loadLibrariesAsync(Library... libraries) { - val futures = new ArrayList>(); - for (val library : libraries) { - val task = new DependencyLoadTask(library.loadingModId, - library.groupId, - library.artifactId, - library.minVersion, - library.maxVersion, - library.preferredVersion, - library.regularSuffix, - library.devSuffix, - false); - futures.add(CompletableFuture.supplyAsync(task::load, executor)); + val libs = new com.falsepattern.deploader.Library[libraries.length]; + for (int i = 0; i < libraries.length; i++) { + libs[i] = adaptLibrary(libraries[i]); } - return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).thenAccept(ignored -> { - var tasks = futures.stream().map(it -> it.getNow(null)).filter(Objects::nonNull).collect(Collectors.toSet()); - if (!tasks.isEmpty()) { - var deps = scanDeps(tasks.stream()); - while (deps != null) { - deps = executeArtifactLoading(new ArrayList<>(deps), true); - } - } - }); + return com.falsepattern.deploader.DependencyLoaderImpl.loadLibrariesAsync(libs); } - private static boolean scanForDepSpecs(URL source, List output, List jijURLs) { - if (!source.getProtocol().equals("file")) { - return false; + /** + * @since 0.10.0 + */ + public static void loadLibraries(Library... libraries) { + val libs = new com.falsepattern.deploader.Library[libraries.length]; + for (int i = 0; i < libraries.length; i++) { + libs[i] = adaptLibrary(libraries[i]); } - val fileName = source.getFile(); - Path path; - - try { - path = Paths.get(source.toURI()); - } catch (URISyntaxException e) { - LOG.error("Could not scan URL " + source + " for dependencies", e); - return false; - } - if (alreadyScannedSources.contains(path)) - return true; - alreadyScannedSources.add(path); - boolean found = false; - if (fileName.endsWith(".jar")) { - //Scan jar file for json in META-INF, add them to the list - try (val inputStream = new BufferedInputStream(source.openStream(), 65536); - val jarFile = new JarInputStream(inputStream, false)) { - ZipEntry entry; - while ((entry = jarFile.getNextEntry()) != null) { - val name = entry.getName(); - if (!name.startsWith("META-INF/")) - continue; - if (name.endsWith(".json") && name.matches("META-INF/\\w+.json")) { - try { - output.add(new URL("jar:" + source + "!/" + entry.getName())); - found = true; - } catch (MalformedURLException e) { - LOG.error("Failed to add json source {} to dependency source list: {}", entry.getName(), e); - } - } else if (name.equals("META-INF/falsepatternlib_repo/")) { - try { - jijURLs.add(new URL("jar:" + source + "!/" + entry.getName())); - } catch (MalformedURLException e) { - LOG.error("Failed to add jar-in-jar repo {}: {}", entry.getName(), e); - } - } - } - } catch (IOException e) { - LOG.error("Failed to open jar file {}", source.getPath()); - } - } else { - if (!Files.exists(path) || !Files.isDirectory(path)) { - LOG.warn("Skipping non-directory, nor jar source: {}", source); - return false; - } - //Scan directory for json in META-INF, add them to the list - val metaInf = path.resolve("META-INF"); - if (!Files.exists(metaInf) || !Files.isDirectory(metaInf)) { - return false; - } - try (val files = Files.list(metaInf)) { - found = files.reduce(false, (prev,file) -> { - val entryFileName = file.getFileName().toString(); - if (entryFileName.endsWith(".json")) { - try { - output.add(file.toUri().toURL()); - return true; - } catch (MalformedURLException e) { - LOG.error("Failed to add json source {} to dependency source list: {}", - file.toString(), - e); - } - } else if (entryFileName.equals("falsepatternlib_repo")) { - try { - jijURLs.add(file.toUri().toURL()); - } catch (MalformedURLException e) { - LOG.error("Failed to add jar-in-jar repo {}: {}", file.toString(), e); - } - } - return prev; - }, (a,b) -> a || b); - } catch (IOException e) { - LOG.error("Failed to open directory {}", metaInf); - } - } - return found; - } - - private static Stream grabSourceCandidatesFromFolder(Path folder) { - if (!Files.exists(folder) || !Files.isDirectory(folder)) { - return Stream.empty(); - } - Stream paths; - try (val files = Files.list(folder)) { - //Lazy loading is bad here - paths = files.collect(Collectors.toSet()).stream(); - } catch (IOException ignored) { - return Stream.empty(); - } - - return paths.map(file -> { - try { - return file.toUri().toURL(); - } catch (MalformedURLException e) { - return null; - } - }).filter(Objects::nonNull); - } - - private static Stream> flatMap(Pair pair, Function> mapper) { - val a = pair.a; - return mapper.apply(pair.b).map(b -> new Pair<>(a, b)); - } - - private static final Pattern VERSION_PATTERN = - Pattern.compile("(0|[1-9]\\d*)" + - "(?:\\.(0|[1-9]\\d*))?" + - "(?:\\.(0|[1-9]\\d*))?" + - "(?:-((?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\\.(?:0|[1-9]\\d*|\\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?" + - "(?:\\+([0-9a-zA-Z-]+(?:\\.[0-9a-zA-Z-]+)*))?"); - - private enum DependencySide { - COMMON, - CLIENT, - SERVER - } - - private enum DependencyScope { - ALWAYS, - DEV, - OBF - } - - @RequiredArgsConstructor - private static class ScopeSide { - final DependencyScope scope; - final DependencySide side; - - boolean contains(ScopeSide dependency) { - return (dependency.scope == scope || dependency.scope == DependencyScope.ALWAYS) && - (dependency.side == side || dependency.side == DependencySide.COMMON); - } - } - - private static boolean initialScan = false; - private static final List> baseTasks = new ArrayList<>(); - - public static void executeDependencyLoading() { - if (!initialScan) { - initialScan = true; - val tasks = scanDeps(); - if (tasks != null) { - baseTasks.addAll(tasks); - } - } - - var tasks = executeArtifactLoading(baseTasks, false); - while (tasks != null) { - baseTasks.addAll(tasks); - tasks = executeArtifactLoading(baseTasks, false); - } - if (modDownloaded.get()) { - if (!SystemUtils.IS_OS_MAC) { - try { - JOptionPane.showMessageDialog(null, "A minecraft mod has been downloaded by the FalsePatternLib dependency downloader.\nYou must restart the game to apply these changes.", "Mod Dependency Download notice", JOptionPane.WARNING_MESSAGE); - } catch (Exception ignored) {} - } - throw new ModDependencyDownloaded("A minecraft mod has been downloaded by the FalsePatternLib dependency downloader, and requires a game restart to get installed properly."); - } - } - - private static class ModDependencyDownloaded extends Error { - public ModDependencyDownloaded(String message) { - super(message); - } - } - - private static @Nullable Set> scanDeps() { - LOG.debug("Discovering dependency source candidates..."); - val modsDir = FileUtil.getMinecraftHomePath().resolve("mods"); - val mods1710Dir = modsDir.resolve("1.7.10"); - return scanDeps(Stream.of(LowLevelCallMultiplexer.getClassPathSources().stream(), - grabSourceCandidatesFromFolder(modsDir), - grabSourceCandidatesFromFolder(mods1710Dir)) - .flatMap(i -> i)); - } - - private static @Nullable Set> scanDeps(Stream candidatesUnfiltered) { - long start = System.currentTimeMillis(); - val urlsWithoutDeps = new HashSet(); - val depCache = tempDir.resolve(".depscan_cache"); - if (Files.exists(depCache)) { - try { - urlsWithoutDeps.addAll(Files.readAllLines(depCache)); - } catch (IOException e) { - LOG.error("Could not read dependency scanner cache", e); - } - } - val candidates = candidatesUnfiltered - .filter(Objects::nonNull) - .filter((url) -> !urlsWithoutDeps.contains(url.toString())) - .collect(Collectors.toList()); - val urls = new ArrayList(); - val jijURLs = new ArrayList(); - for (val candidate : candidates) { - if (!scanForDepSpecs(candidate, urls, jijURLs)) { - urlsWithoutDeps.add(candidate.toString()); - } - } - try (val out = Files.newBufferedWriter(depCache)) { - for (val noDep : urlsWithoutDeps) { - out.append(noDep).append(System.lineSeparator()); - } - } catch (IOException e) { - LOG.error("Could not write dependency scanner cache", e); - } - val javaVersion = LowLevelCallMultiplexer.javaMajorVersion(); - val dependencySpecs = urls.stream().map((source) -> { - //Convert source to GSON json - try (val is = new BufferedInputStream(source.openStream())) { - val jsonRaw = new JsonParser().parse(new InputStreamReader(is)); - if (!jsonRaw.isJsonObject()) { - return null; - } - val json = jsonRaw.getAsJsonObject(); - if (!(json.has("identifier") && json.get("identifier") - .getAsString() - .equals("falsepatternlib_dependencies"))) { - return null; - } - val builder = new GsonBuilder(); - builder.excludeFieldsWithoutExposeAnnotation(); - val gson = builder.create(); - json.remove("identifier"); - val root = gson.fromJson(json, DepRoot.class); - val minJ = root.minJava(); - val maxJ = root.maxJava(); - if (minJ != null && minJ > javaVersion || maxJ != null && maxJ < javaVersion) { - return null; - } - root.source(source.toString()); - return root; - } catch (Exception e) { - LOG.error("Failed to read json from source {}: {}", source, e); - return null; - } - }).filter(Objects::nonNull).collect(Collectors.toSet()); - long end = System.currentTimeMillis(); - LOG.debug("Discovered {} dependency source candidates in {}ms", dependencySpecs.size(), end - start); - remoteMavenRepositories.addAll(dependencySpecs.stream() - .flatMap((dep) -> dep.repositories().stream()) - .map(repo -> repo.endsWith("/") ? repo : repo + "/") - .collect(Collectors.toSet())); - localMavenRepositories.addAll(jijURLs.stream().map(URL::toString).map(repo -> repo.endsWith("/") ? repo : repo + "/").collect(Collectors.toSet())); - for (DepRoot dependencySpec : dependencySpecs) { - val bundled = dependencySpec.bundledArtifacts(); - if (bundled == null) { - continue; - } - for (String artifact : bundled) { - val parts = artifact.split(":"); - if (parts.length < 3) { - LOG.error("Invalid bundled artifact: {}", dependencySpec); - continue; - } - val groupId = parts[0]; - val artifactId = parts[1]; - Version version = parseVersion(parts[2]); - final String classifier = parts.length > 3 ? parts[3] : null; - if (classifier != null) { - LOG.info("Found bundled artifact: {}:{}:{}:{} from {}", - groupId, - artifactId, - version, - classifier, - dependencySpec.source()); - } else { - LOG.info("Found bundled artifact: {}:{}:{} from {}", - groupId, - artifactId, - version, - dependencySpec.source()); - } - val id = groupId + ":" + artifactId + ":" + classifier; - if (!loadedLibraries.containsKey(id)) { - loadedLibraries.put(id, version); - } - if (!loadedLibraryMods.containsKey(id)) { - loadedLibraryMods.put(id, dependencySpec.source()); - } - } - } - val artifacts = dependencySpecs.stream() - .flatMap(it -> Stream.of( - new SourcedDep(it.source(), it.dependencies(), false), - new SourcedDep(it.source(), it.modDependencies(), true))) - .filter(it -> it.dep != null) - .flatMap(it -> Stream.of( - new ScopedDep(it.source, it.mod, DependencyScope.ALWAYS, it.dep.always()), - new ScopedDep(it.source, it.mod, DependencyScope.DEV, it.dep.dev()), - new ScopedDep(it.source, it.mod, DependencyScope.OBF, it.dep.obf()))) - .flatMap(it -> Stream.concat(it.deps.common().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.COMMON), dep)), - Stream.concat( - it.deps.client().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.CLIENT), dep)), - it.deps.server().stream().map(dep -> new ScopedSidedDep(it.source, it.mod, new ScopeSide(it.scope, DependencySide.SERVER), dep)) - ))) - .map((scopedSidedDep) -> { - val source = scopedSidedDep.source; - val scope = scopedSidedDep.scope; - val dep = scopedSidedDep.dep; - val parts = dep.split(":"); - if (parts.length < 3) { - LOG.error("Invalid dependency: {}", dep); - return null; - } - val groupId = parts[0]; - val artifactId = parts[1]; - Version version = parseVersion(parts[2]); - final String classifier = parts.length > 3 ? parts[3] : null; - if (classifier != null) { - LOG.info("Found dependency: {}:{}:{}:{} from {}", - groupId, - artifactId, - version, - classifier, - source); - } else { - LOG.info("Found dependency: {}:{}:{} from {}", - groupId, - artifactId, - version, - source); - } - return new Pair<>(scope, - new DependencyLoadTask(source, - groupId, - artifactId, - version, - null, - version, - classifier, - classifier, - scopedSidedDep.mod)); - }) - .filter(Objects::nonNull) - .collect(Collectors.toSet()); - if (artifacts.isEmpty()) { - return null; - } - return artifacts; - } - - @RequiredArgsConstructor - private static class SourcedDep { - public final String source; - public final DepRoot.Dependencies dep; - public final boolean mod; - } - - @RequiredArgsConstructor - private static class ScopedDep { - public final String source; - public final boolean mod; - public final DependencyScope scope; - public final DepRoot.SidedDependencies deps; - } - - @RequiredArgsConstructor - private static class ScopedSidedDep { - public final String source; - public final boolean mod; - public final ScopeSide scope; - public final String dep; + com.falsepattern.deploader.DependencyLoaderImpl.loadLibraries(libs); } public static Version parseVersion(String versionString) { - try { - val matcher = VERSION_PATTERN.matcher(versionString); - if (!matcher.matches()) { - return new RawVersion(versionString); - } - val major = Integer.parseInt(matcher.group(1)); - val minor = matcher.group(2) == null ? -1 - : Integer.parseInt(matcher.group(2)); - val patch = matcher.group(3) == null ? -1 - : Integer.parseInt(matcher.group(3)); - val preRelease = matcher.group(4); - val build = matcher.group(5); - return new SemanticVersion(major, minor, patch, preRelease, build); - } catch (Exception e) { - return new RawVersion(versionString); - } + val realVersion = com.falsepattern.deploader.DependencyLoaderImpl.parseVersion(versionString); + return adaptVersion(realVersion); } - private static class SideAwareAssistant { - static ScopeSide current() { - return new ScopeSide(Share.DEV_ENV ? DependencyScope.DEV : DependencyScope.OBF, Share.CLIENT ? DependencySide.CLIENT : DependencySide.SERVER); - } - } - - private static @Nullable Set> executeArtifactLoading(List> tasks, boolean silent) { - val scopeSide = SideAwareAssistant.current(); - val iter = tasks.iterator(); - val artifactMap = new HashMap(); - while (iter.hasNext()) { - val artifact = iter.next(); - val artifactSide = artifact.a; - if (!scopeSide.contains(artifactSide)) - continue; - iter.remove(); - val artifactPayload = artifact.b; - val id = artifactPayload.getGroupArtifact(); - if (artifactMap.containsKey(id)) { - val otherArtifact = artifactMap.get(id); - //TODO: Check for conflicts - if (artifactPayload.preferredVersion.compareTo(otherArtifact.preferredVersion) > 0) { - LOG.info("Replacing dependency {}:{} from {} with version {} from {}", - otherArtifact.getGroupArtifact(), - otherArtifact.preferredVersion, - otherArtifact.loadingModId, - artifactPayload.preferredVersion, - artifactPayload.loadingModId); - artifactMap.put(id, artifactPayload); - } - } else { - artifactMap.put(id, artifactPayload); - } - } - if (artifactMap.isEmpty()) + private static @Nullable Version adaptVersion(@Nullable com.falsepattern.deploader.version.Version version) { + if (version == null) return null; - val futures = new ArrayList>(); - if (!silent) { - LOG.info("-----------------------------------------------------------"); - LOG.info("FalsePatternLib is downloading dependencies. Please wait..."); - LOG.info("-----------------------------------------------------------"); + if (version instanceof com.falsepattern.deploader.version.RawVersion raw) { + return new RawVersion(raw.versionString); } - JFrame theFrame = null; - val progresses = new HashMap(); - if (silent) { - - } else if (SystemUtils.IS_OS_MAC) { - LOG.info("MacOS detected, not creating progress window (your OS is buggy)"); - } else { - try { - val jFrame = new JFrame("Dependency Download"); - val constraints = new GridBagConstraints(); - jFrame.getContentPane().setLayout(new GridBagLayout()); - constraints.gridy = 0; - constraints.gridwidth = 2; - jFrame.add(new JLabel("FalsePatternLib is downloading dependencies, please wait!"), constraints); - constraints.gridwidth = 1; - for (val artifact : artifactMap.entrySet()) { - constraints.gridy++; - jFrame.add(new JLabel(artifact.getKey()), constraints); - val status = new JProgressBar(); - status.setIndeterminate(true); - status.setStringPainted(true); - status.setString("Waiting..."); - progresses.put(artifact.getValue(), status); - jFrame.add(status, constraints); - } - jFrame.pack(); - jFrame.setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE); - Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); - jFrame.setLocation(dim.width/2-jFrame.getSize().width/2, dim.height/2-jFrame.getSize().height/2); - theFrame = jFrame; - } catch (Exception ignored) { + if (version instanceof com.falsepattern.deploader.version.SemanticVersion sem) { + return new SemanticVersion(sem.getMajorVersion(), sem.getMinorVersion(), sem.getPatchVersion(), sem.getPreRelease(), sem.getBuild()); + } + if (version instanceof com.falsepattern.deploader.version.ComplexVersion cplx) { + val versA = cplx.versions; + val vers = new Version[versA.length]; + for (int i = 0; i < versA.length; i++) { + vers[i] = adaptVersion(versA[i]); } + return new ComplexVersion(vers); } - if (theFrame != null) { - for (val task : artifactMap.values()) { - futures.add(CompletableFuture.supplyAsync(() -> { - val bar = progresses.get(task); - bar.setString("Downloading..."); - val res = task.load(); - bar.setMinimum(0); - bar.setMaximum(1); - bar.setValue(1); - bar.setString("Completed!"); - return res; - }, executor)); - } - } else { - for (val task : artifactMap.values()) { - futures.add(CompletableFuture.supplyAsync(task::load, executor)); - } - } - val theFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])) - .thenApply(ignored -> - futures.stream() - .map(it -> it.getNow(null)) - .collect(Collectors.toList()) - ); - AtomicBoolean doViz = new AtomicBoolean(true); - if (theFrame != null) { - final var vizThread = getVizThread(doViz, progresses, theFrame); - vizThread.start(); - } - List res; - try { - res = theFuture.join(); - } catch (Error e) { - throw e; - } catch (Throwable t) { - throw new Error(t); - } finally { - if (theFrame != null) { - doViz.set(false); - } - } - return scanDeps(res.stream()); + throw new IllegalStateException("Unknown version class " + version.getClass().getName()); } - @NotNull - private static Thread getVizThread(AtomicBoolean doViz, HashMap progresses, JFrame theFrame) { - val vizThread = new Thread(() -> { - int waitBeforeShowing = 100; - while (doViz.get()) { - if (waitBeforeShowing > 0) { - waitBeforeShowing--; - } else if (waitBeforeShowing == 0) { - theFrame.setVisible(true); - waitBeforeShowing = -1; - } - for (val progress : progresses.entrySet()) { - val task = progress.getKey(); - val bar = progress.getValue(); - if (task.contentLength == -1) { - bar.setIndeterminate(true); - } else { - bar.setIndeterminate(false); - bar.setMinimum(0); - bar.setMaximum((int) task.contentLength); - bar.setValue((int) task.downloaded); - } - } - theFrame.repaint(); - try { - Thread.sleep(10); - } catch (InterruptedException ignored) { - } + @Contract("null -> null;!null -> !null") + private static @Nullable com.falsepattern.deploader.version.Version adaptVersion(@Nullable Version version) { + if (version == null) + return null; + if (version instanceof RawVersion raw) { + return new com.falsepattern.deploader.version.RawVersion(raw.versionString); + } + if (version instanceof SemanticVersion sem) { + return new com.falsepattern.deploader.version.SemanticVersion(sem.getMajorVersion(), sem.getMinorVersion(), sem.getPatchVersion(), sem.getPreRelease(), sem.getBuild()); + } + if (version instanceof ComplexVersion cplx) { + val versA = cplx.versionsRaw(); + val vers = new com.falsepattern.deploader.version.Version[versA.length]; + for (int i = 0; i < versA.length; i++) { + vers[i] = adaptVersion(versA[i]); } - theFrame.dispose(); - }); - vizThread.setDaemon(true); - vizThread.setName("FalsePatternLib Download Visualizer"); - return vizThread; + return new com.falsepattern.deploader.version.ComplexVersion(vers); + } + throw new IllegalStateException("Unknown version class " + version.getClass().getName()); } - @RequiredArgsConstructor - private static class DependencyLoadTask { - @NonNull - private final String loadingModId; - @NonNull - private final String groupId; - @NonNull - private final String artifactId; - @NonNull - private final Version minVersion; - private final Version maxVersion; - @NonNull - private final Version preferredVersion; - private final String regularSuffix; - private final String devSuffix; - private final boolean isMod; - - private String suffix; - private String artifactLogName; - private String artifact; - private String mavenJarName; - public volatile String jarName; - private Path file; - - public volatile long contentLength = -1; - public volatile long downloaded = 0; - - private @Nullable URL load() { - setupLibraryNames(); - if (loadedLibraries.containsKey(artifact)) { - alreadyLoaded(); - return null; - } - setupPaths(); - for (val repo: localMavenRepositories) { - val url = tryDownloadFromMaven(repo, true); - if (url != null) { - return url; - } - } - val existingUrl = tryLoadingExistingFile(); - if (existingUrl != null) { - return existingUrl; - } - validateDownloadsAllowed(); - for (var repo : remoteMavenRepositories) { - val url = tryDownloadFromMaven(repo, false); - if (url != null) { - return url; - } - } - throw crashCouldNotDownload(); - } - - private IllegalStateException crashCouldNotDownload() { - val errorMessage = "Failed to download library " + groupId + ":" + artifactId + ":" + preferredVersion + ( - (suffix != null) ? ":" + suffix : "") + " from any repository! Requested by mod: " + loadingModId; - LOG.fatal(errorMessage); - return new IllegalStateException(errorMessage); - } - - private void setupLibraryNames() { - suffix = Share.DEV_ENV ? devSuffix : regularSuffix; - artifactLogName = String.format("%s:%s:%s%s", - groupId, - artifactId, - preferredVersion, - suffix != null ? "-" + suffix : ""); - if (!isMod) { - LOG.info("Adding library {}, requested by mod {}", artifactLogName, loadingModId); - } - artifact = groupId + ":" + artifactId + ":" + suffix; - } - - private void alreadyLoaded() { - val currentVer = loadedLibraries.get(artifact); - if (currentVer.equals(preferredVersion)) { - return; - } - val rangeString = "(minimum: " + minVersion + (maxVersion == null ? "" : ", maximum: " + maxVersion) + ")"; - if (minVersion.compareTo(currentVer) > 0 || (maxVersion != null && maxVersion.compareTo(currentVer) < 0)) { - for (int i = 0; i < 16; i++) { - LOG.fatal("ALERT VVVVVVVVVVVV ALERT"); - } - LOG.fatal("Library {}:{}{} already loaded with version {}, " - + "but a version in the range {} was requested! Thing may go horribly wrong! " - + "Requested by mod: {}, previously loaded by mod: {}", - groupId, - artifactId, - suffix != null ? ":" + suffix : "", - currentVer, - rangeString, - loadingModId, - loadedLibraryMods.get(artifact)); - for (int i = 0; i < 16; i++) { - LOG.fatal("ALERT ^^^^^^^^^^^^ ALERT"); - } - } else { - LOG.info("Attempted loading of library {}:{}{} with preferred version {}, " - + "but version {} was already loaded, which matched the range {}. This is not an " - + "error. " - + "Requested by mod: {}, previously loaded by mod: {}", - groupId, - artifactId, - suffix != null ? ":" + suffix : "", - preferredVersion, - currentVer, - rangeString, - loadingModId, - loadedLibraryMods.get(artifact)); - } - } - - private void setupPaths() { - mavenJarName = - String.format("%s-%s%s.jar", artifactId, preferredVersion, (suffix != null) ? ("-" + suffix) : ""); - jarName = groupId + "-" + mavenJarName; - file = (isMod ? modsDir : libDir).resolve(jarName); - } - - private @Nullable URL tryLoadingExistingFile() { - if (!Files.exists(file)) { - return null; - } - try { - val status = validateChecksum(file); - if (status == ChecksumStatus.FAILED) { - return null; - } else if (status == ChecksumStatus.MISSING) { - LOG.debug("Library {} is missing checksum data! Either it was manually deleted, " - + "or the source repo didn't have it in the first place", artifactLogName); - } - } catch (IOException e) { - LOG.error("Failed to execute validation check for " + artifactLogName, e); - checkedDelete(file); - return null; - } - try { - val theUrl = file.toUri().toURL(); - if (!isMod) { - addToClasspath(theUrl); - } - loadedLibraries.put(artifact, preferredVersion); - LOG.debug("Library {} successfully loaded from disk!", artifactLogName); - return theUrl; - } catch (Exception e) { - LOG.warn("Failed to load library {} from file! Re-downloading...", artifactLogName); - checkedDelete(file); - return null; - } - } - - private void validateDownloadsAllowed() { - if (!EarlyConfig.getInstance().enableLibraryDownloads()) { - val errorMessage = "Failed to load library " - + groupId - + ":" - + artifactId - + ":" - + preferredVersion - + ((suffix != null) ? ":" + suffix : "") - + ": " - + Tags.MODNAME - + " library downloading has been disabled in the config, and the library is not present " - + "on disk! Requested by mod: " - + loadingModId; - LOG.fatal(errorMessage); - throw new IllegalStateException(errorMessage); - } - } - - private static final Object mutex = new Object(); - private @Nullable URL tryDownloadFromMaven(String repo, boolean local) { - synchronized (mutex) { - try { - if (!repo.endsWith("/")) { - repo = repo + "/"; - } - val url = String.format("%s%s/%s/%s/%s", - repo, - groupId.replace('.', '/'), - artifactId, - preferredVersion, - mavenJarName); - String finalRepo = repo; - int retryCount = 0; - while (true) { - retryCount++; - if (retryCount > 3) { - break; - } - val success = new AtomicBoolean(false); - val tmpFile = file.getParent().resolve(file.getFileName().toString() + ".tmp"); - if (Files.exists(tmpFile)) { - Files.delete(tmpFile); - } - Internet.connect(new URL(url), - ex -> LOG.debug("Artifact {} could not be downloaded from repo {}: {}", - artifactLogName, - finalRepo, - ex.getMessage()), - input -> { - LOG.debug("Downloading {} from {}", artifactLogName, finalRepo); - download(input, tmpFile, d -> downloaded += d); - LOG.debug("Downloaded {} from {}", artifactLogName, finalRepo); - success.set(true); - }, - contentLength -> this.contentLength = contentLength); - if (success.get()) { - if (FileUtils.contentEquals(tmpFile.toFile(), file.toFile())) { - Files.delete(tmpFile); - } else { - if (isMod) { - modDownloaded.set(true); - } - try { - Files.move(tmpFile, file, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException ignored) { - Files.move(tmpFile, file); - } - } - LOG.debug("Validating checksum for {}", artifactLogName); - val hadChecksum = validateChecksum(url); - switch (hadChecksum) { - case FAILED: - continue; - case OK: - break; - case MISSING: - if (!local) { - LOG.warn("The library {} had no checksum available on the repository.\n" - + "There's a chance it might have gotten corrupted during download,\n" - + "but we're loading it anyways.", artifactLogName); - } - } - loadedLibraries.put(artifact, preferredVersion); - loadedLibraryMods.put(artifact, loadingModId); - val diskUrl = file.toUri().toURL(); - if (!isMod) { - addToClasspath(diskUrl); - } - return diskUrl; - } - } - } catch (IOException ignored) { - } - return null; - } - } - - private ChecksumStatus validateChecksum(String url) throws IOException { - for (val checksumType : CHECKSUM_TYPES) { - val checksumURL = url + "." + checksumType; - val checksumFile = libDir.resolve(jarName + "." + checksumType); - LOG.debug("Attempting to get {} checksum...", checksumType); - val success = new AtomicBoolean(false); - Internet.connect(new URL(checksumURL), - (ex) -> LOG.debug("Could not get {} checksum for {}: {}", - checksumType, - artifactLogName, - ex.getMessage()), - (input) -> { - LOG.debug("Downloading {} checksum for {}", checksumType, artifactLogName); - download(input, checksumFile, d -> {}); - LOG.debug("Downloaded {} checksum for {}", checksumType, artifactLogName); - success.set(true); - }, - length -> {}); - if (success.get()) { - return getChecksumStatus(file, checksumType, checksumFile); - } - } - return ChecksumStatus.MISSING; - } - - private ChecksumStatus validateChecksum(Path file) throws IOException { - for (val checksumType : CHECKSUM_TYPES) { - val checksumFile = libDir.resolve(jarName + "." + checksumType); - LOG.debug("Attempting to read {} checksum from file...", checksumType); - if (Files.exists(checksumFile)) { - return getChecksumStatus(file, checksumType, checksumFile); - } - } - return ChecksumStatus.MISSING; - } - - private ChecksumStatus getChecksumStatus(Path file, String checksumType, Path checksumFile) throws IOException { - val fileHash = hash(checksumType, file); - val referenceHash = new String(Files.readAllBytes(checksumFile)); - if (!fileHash.equals(referenceHash)) { - LOG.error("Failed {} checksum validation for {}.", checksumType, artifactLogName); - checkedDelete(file); - checkedDelete(checksumFile); - return ChecksumStatus.FAILED; - } - LOG.debug("Successfully validated {} checksum for {}.", checksumType, artifactLogName); - return ChecksumStatus.OK; - } - - public String getGroupArtifact() { - return groupId + ":" + artifactId; - } - - private enum ChecksumStatus { - OK, - FAILED, - MISSING - } + private static com.falsepattern.deploader.Library adaptLibrary(Library library) { + return new com.falsepattern.deploader.Library( + library.loadingModId, + library.groupId, + library.artifactId, + adaptVersion(library.minVersion), + adaptVersion(library.maxVersion), + adaptVersion(library.preferredVersion), + library.regularSuffix, + library.devSuffix + ); } }