diff --git a/dependencies.gradle b/dependencies.gradle index bd93382..6b6720f 100644 --- a/dependencies.gradle +++ b/dependencies.gradle @@ -7,6 +7,8 @@ dependencies { compileOnly("org.jetbrains:annotations:24.0.1") + compileOnly("com.gtnewhorizons.retrofuturabootstrap:RetroFuturaBootstrap:1.0.2") + //Mod deps def unimixinsVersion = "0.1.17" compileOnly("com.github.LegacyModdingMC.UniMixins:unimixins-all-1.7.10:$unimixinsVersion:dev") diff --git a/repositories.gradle b/repositories.gradle index d6151d0..7321f9d 100644 --- a/repositories.gradle +++ b/repositories.gradle @@ -15,4 +15,11 @@ repositories { name = "repsy_ventooth" url = "https://repo.repsy.io/mvn/ventooth/public/" } + maven { + name = "horizon" + url = "https://mvn.falsepattern.com/horizon/" + content { + includeGroup("com.gtnewhorizons.retrofuturabootstrap") + } + } } diff --git a/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java b/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java index 7a38cb7..c663d31 100644 --- a/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java +++ b/src/main/java/com/falsepattern/lib/config/event/ConfigValidationFailureEvent.java @@ -23,6 +23,7 @@ package com.falsepattern.lib.config.event; import com.falsepattern.lib.StableAPI; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.internal.EventUtil; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import com.falsepattern.lib.text.FormattedText; import com.falsepattern.lib.toasts.GuiToast; @@ -131,7 +132,7 @@ public class ConfigValidationFailureEvent extends Event { } customText(errorString); for (val line : errorString.toString().split("\n")) { - Share.LOG.error(line); + FPLog.LOG.error(line); } } diff --git a/src/main/java/com/falsepattern/lib/internal/FPLog.java b/src/main/java/com/falsepattern/lib/internal/FPLog.java new file mode 100644 index 0000000..eb901b4 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/FPLog.java @@ -0,0 +1,36 @@ +/* + * Copyright (C) 2022-2023 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions of the Software. + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.lib.internal; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public final class FPLog { + public static final Logger LOG = LogManager.getLogger(Tags.MODNAME); + + public static void deprecatedWarning(Throwable stacktrace) { + LOG.warn("DEPRECATED API CALLED!", stacktrace); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java index 6ec99c2..ba0c433 100644 --- a/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java +++ b/src/main/java/com/falsepattern/lib/internal/FalsePatternLib.java @@ -52,7 +52,7 @@ public class FalsePatternLib { private static CommonProxy proxy; public FalsePatternLib() { - Share.LOG.info("Version " + Tags.VERSION + " initialized!"); + FPLog.LOG.info("Version " + Tags.VERSION + " initialized!"); } @Mod.EventHandler diff --git a/src/main/java/com/falsepattern/lib/internal/Internet.java b/src/main/java/com/falsepattern/lib/internal/Internet.java index e10fc8f..f95f4d7 100644 --- a/src/main/java/com/falsepattern/lib/internal/Internet.java +++ b/src/main/java/com/falsepattern/lib/internal/Internet.java @@ -45,22 +45,11 @@ import java.util.function.Consumer; public class Internet { private static final Map NO_HEADERS = Collections.emptyMap(); - public static void connect(URL URL, Consumer onError, Consumer onSuccess) { - connect(URL, NO_HEADERS, onError, onSuccess); + public static void connect(URL URL, Consumer onError, Consumer onSuccess, Consumer contentLengthCallback) { + connect(URL, NO_HEADERS, onError, onSuccess, contentLengthCallback); } - public static Map constructHeaders(String... entries) { - if (entries.length % 2 != 0) { - throw new IllegalArgumentException("Entries must be in pairs"); - } - val map = new java.util.HashMap(); - for (int i = 0; i < entries.length; i += 2) { - map.put(entries[i], entries[i + 1]); - } - return map; - } - - public static void connect(URL URL, Map headers, Consumer onError, Consumer onSuccess) { + public static void connect(URL URL, Map headers, Consumer onError, Consumer onSuccess, Consumer contentLengthCallback) { try { val connection = (HttpURLConnection) URL.openConnection(); connection.setConnectTimeout(3500); @@ -85,6 +74,7 @@ public class Internet { if (connection.getResponseCode() != 200) { onError.accept(new Exception("HTTP response code " + connection.getResponseCode())); } else { + contentLengthCallback.accept(connection.getContentLengthLong()); onSuccess.accept(connection.getInputStream()); } connection.disconnect(); @@ -103,57 +93,14 @@ public class Internet { } } - public static CompletableFuture download(URL... urls) { - return CompletableFuture.supplyAsync(() -> { - val result = new ByteArrayOutputStream(); - val caught = new AtomicReference(); - val success = new AtomicBoolean(false); - for (val url : urls) { - connect(url, caught::set, (input) -> { - try { - transferAndClose(input, result); - success.set(true); - } catch (IOException e) { - throw new CompletionException(e); - } - }); - if (success.get()) { - return result.toByteArray(); - } - } - throw new CompletionException(caught.get()); - }); - } - - public static CompletableFuture downloadFile(File file, URL... urls) { - return CompletableFuture.runAsync(() -> { - val caught = new AtomicReference(); - val success = new AtomicBoolean(false); - for (val url : urls) { - connect(url, caught::set, (input) -> { - try { - @Cleanup val outputStream = Files.newOutputStream(file.toPath()); - transferAndClose(input, outputStream); - success.set(true); - } catch (IOException e) { - throw new CompletionException(e); - } - }); - if (success.get()) { - return; - } - } - throw new CompletionException(caught.get()); - }); - } - - - public static void transferAndClose(InputStream is, OutputStream target) throws IOException { + public static void transferAndClose(InputStream is, OutputStream target, Consumer downloadSizeCallback) + throws IOException { var bytesRead = 0; - byte[] smallBuffer = new byte[4096]; + byte[] smallBuffer = new byte[256 * 1024]; while ((bytesRead = is.read(smallBuffer)) >= 0) { target.write(smallBuffer, 0, bytesRead); + downloadSizeCallback.accept(bytesRead); } target.close(); is.close(); 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 38d168c..7b6b3c9 100644 --- a/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java +++ b/src/main/java/com/falsepattern/lib/internal/asm/CoreLoadingPlugin.java @@ -20,6 +20,7 @@ */ package com.falsepattern.lib.internal.asm; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import com.falsepattern.lib.internal.Tags; import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl; @@ -48,19 +49,19 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { private static boolean obfuscated; static { - Share.LOG.info("Removing skill issues..."); + FPLog.LOG.info("Removing skill issues..."); try { Class.forName("thermos.Thermos"); - Share.LOG.fatal("Sorry, i prefer iced coffee."); + FPLog.LOG.fatal("Sorry, i prefer iced coffee."); throw skillIssue("Thermos is not supported by FalsePatternLib, please use a normal forge server."); } catch (ClassNotFoundException ignored) { } //Scan for dependencies now - Share.LOG.info("Scanning for deps..."); + FPLog.LOG.info("Scanning for deps..."); long start = System.nanoTime(); - DependencyLoaderImpl.scanDeps(); + DependencyLoaderImpl.executeDependencyLoading(true); long end = System.nanoTime(); - Share.LOG.info("Scanned in " + (end - start) / 1000000 + "ms"); + FPLog.LOG.info("Scanned in " + (end - start) / 1000000 + "ms"); //Initializing the rest MappingManager.initialize(); } @@ -116,7 +117,7 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { } public static void validateGasStation() { - Share.LOG.info("Got any gas?"); + FPLog.LOG.info("Got any gas?"); //Make sure everything is loaded correctly, crash if gasstation is bugged // @formatter:off if (!isClassPresentSafe("com.falsepattern.gasstation.core.GasStationCore") //Validate core class @@ -126,7 +127,7 @@ public class CoreLoadingPlugin implements IFMLLoadingPlugin { || !isClassPresentSafe("org.spongepowered.asm.lib.Opcodes") //Validate correct mixins class || isClassPresentSafe("org.spongepowered.libraries.org.objectweb.asm.Opcodes") ) { - Share.LOG.fatal("Somebody put diesel in my gas tank!"); + FPLog.LOG.fatal("Somebody put diesel in my gas tank!"); throw new Error("Failed to validate your GasStation mixin plugin installation. " + "Please make sure you have the latest GasStation installed from the official source: " + "https://github.com/FalsePattern/GasStation"); diff --git a/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java b/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java new file mode 100644 index 0000000..99212bd --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/asm/RFBLoadingPlugin.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2022-2023 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions of the Software. + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.lib.internal.asm; + +import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; +import com.falsepattern.lib.internal.impl.dependencies.DependencyLoaderImpl; +import com.gtnewhorizons.retrofuturabootstrap.api.RfbPlugin; + +public class RFBLoadingPlugin implements RfbPlugin { + static { + LowLevelCallMultiplexer.rfbDetected(); + DependencyLoaderImpl.executeDependencyLoading(false); + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java b/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java new file mode 100644 index 0000000..7ce7c49 --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/config/EarlyConfig.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2022-2023 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions of the Software. + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.lib.internal.config; + +import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.internal.core.LowLevelCallMultiplexer; +import com.google.gson.Gson; +import com.google.gson.annotations.Expose; +import lombok.Data; +import lombok.SneakyThrows; +import lombok.experimental.Accessors; +import lombok.val; + +import java.io.FileInputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; + +//For stuff that runs before FML does +@Data +@Accessors(fluent = true) +public class EarlyConfig { + @Expose + @StableAPI.Expose(since = "__INTERNAL__") + private boolean enableLibraryDownloads; + + private static EarlyConfig instance = null; + + @SneakyThrows + public static EarlyConfig load() { + if (instance != null) + return instance; + val configFile = LowLevelCallMultiplexer.gameDir().resolve("config").resolve("falsepatternlib-early.json"); + val gson = new Gson(); + EarlyConfig config; + if (!Files.exists(configFile)) { + config = new EarlyConfig(); + config.enableLibraryDownloads(true); + } else { + config = gson.fromJson(new String(Files.readAllBytes(configFile), StandardCharsets.UTF_8), EarlyConfig.class); + } + Files.write(configFile, gson.toJson(config).getBytes(StandardCharsets.UTF_8)); + instance = config; + return config; + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/config/InGameModOptionsFix.java b/src/main/java/com/falsepattern/lib/internal/config/InGameModOptionsFix.java index ea92de0..442d5cc 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/InGameModOptionsFix.java +++ b/src/main/java/com/falsepattern/lib/internal/config/InGameModOptionsFix.java @@ -1,5 +1,6 @@ package com.falsepattern.lib.internal.config; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import lombok.AccessLevel; import lombok.NoArgsConstructor; @@ -37,7 +38,7 @@ public final class InGameModOptionsFix { MODS_FIELD.setAccessible(true); } catch (NoSuchFieldException e) { MODS_FIELD = null; - Share.LOG.error("Failed to get field: cpw.mods.fml.client.GuiModList.mods," + FPLog.LOG.error("Failed to get field: cpw.mods.fml.client.GuiModList.mods," + " In-Game Mod Options Fix will not work", e); return; } diff --git a/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java b/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java index e0b85eb..5dc1d33 100644 --- a/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java +++ b/src/main/java/com/falsepattern/lib/internal/config/LibraryConfig.java @@ -38,13 +38,6 @@ public class LibraryConfig { @Config.DefaultBoolean(true) public static boolean ENABLE_UPDATE_CHECKER; - @Config.Comment({"Used to control whether FalsePatternLib should be allowed to use the internet.", - "If this is disabled, library downloads will be blocked.", - "Note that if a mod tries to download a library that is not downloaded yet, the game will crash."}) - @Config.LangKey("config.falsepatternlib.disableinternet") - @Config.DefaultBoolean(true) - public static boolean ENABLE_LIBRARY_DOWNLOADS; - @Config.Comment({"Fixes the mod options menu in-game.", "By default, the mod options when in already in a game will show \"Test1, Test2, DISABLED\" in bright red.", "This replaces that interface with the one from the main menu."}) diff --git a/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java b/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java new file mode 100644 index 0000000..9f451ab --- /dev/null +++ b/src/main/java/com/falsepattern/lib/internal/core/LowLevelCallMultiplexer.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2022-2023 FalsePattern + * All Rights Reserved + * + * The above copyright notice and this permission notice + * shall be included in all copies or substantial portions of the Software. + * + * This program 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, either version 3 of the License, or + * (at your option) any later version. + * + * This program 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 General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see . + */ + +package com.falsepattern.lib.internal.core; + +import com.gtnewhorizons.retrofuturabootstrap.RfbApiImpl; +import lombok.experimental.UtilityClass; + +import net.minecraft.launchwrapper.Launch; + +import cpw.mods.fml.relauncher.FMLLaunchHandler; + +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; + +@UtilityClass +public class LowLevelCallMultiplexer { + private static boolean rfbDetected = false; + public static void rfbDetected() { + rfbDetected = true; + } + + public static void addURLToClassPath(URL url) { + if (rfbDetected) { + RFBLowLevel.addURLToClassPath(url); + } else { + LaunchWrapperLowLevel.addURLToClassPath(url); + } + } + + public static List getClassPathSources() { + if (rfbDetected) { + return RFBLowLevel.getClassPathSources(); + } else { + return LaunchWrapperLowLevel.getClassPathSources(); + } + } + + public static Path gameDir() { + if (rfbDetected) { + return RFBLowLevel.gameDir(); + } else { + return LaunchWrapperLowLevel.gameDir(); + } + } + + //Separate classes to avoid accidental classloading + + @SuppressWarnings("resource") + private static class RFBLowLevel { + static void addURLToClassPath(URL url) { + RfbApiImpl.INSTANCE.compatClassLoader().addURL(url); + } + + static List getClassPathSources() { + return RfbApiImpl.INSTANCE.compatClassLoader().getSources(); + } + + static Path gameDir() { + return RfbApiImpl.INSTANCE.gameDirectory(); + } + } + + private static class LaunchWrapperLowLevel { + static void addURLToClassPath(URL url) { + Launch.classLoader.addURL(url); + } + + static List getClassPathSources() { + return Launch.classLoader.getSources(); + } + + static Path gameDir() { + return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath(); + } + } +} diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java index eb58ef2..e6baace 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/ConfigurationManagerImpl.java @@ -23,6 +23,7 @@ package com.falsepattern.lib.internal.impl.config; import com.falsepattern.lib.config.ConfigException; import com.falsepattern.lib.config.event.AllConfigSyncEvent; import com.falsepattern.lib.config.event.ConfigSyncEvent; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.Share; import com.falsepattern.lib.internal.impl.config.net.SyncRequest; @@ -162,7 +163,7 @@ public final class ConfigurationManagerImpl { public static void receiveReply(DataInput input) throws IOException { if (AllConfigSyncEvent.postStart()) { - Share.LOG.warn("All config synchronization was cancelled by event."); + FPLog.LOG.warn("All config synchronization was cancelled by event."); return; } int count = input.readInt(); @@ -172,20 +173,20 @@ public final class ConfigurationManagerImpl { val opt = serializedNames.keySet().stream().filter((key) -> key.equals(serializedName)).findFirst(); if (!opt.isPresent()) { input.skipBytes(dataSize); - Share.LOG.warn("Server tried to sync config not registered on our side: " + serializedName); + FPLog.LOG.warn("Server tried to sync config not registered on our side: " + serializedName); continue; } val clazz = serializedNames.get(opt.get()); val config = parsedConfigMap.get(clazz); if (!config.sync) { input.skipBytes(dataSize); - Share.LOG.warn("Server tried to sync config without @Synchronize annotation on our side: " + FPLog.LOG.warn("Server tried to sync config without @Synchronize annotation on our side: " + serializedName); continue; } if (ConfigSyncEvent.postStart(clazz)) { input.skipBytes(dataSize); - Share.LOG.warn("Config synchronization was cancelled by event for: " + serializedName); + FPLog.LOG.warn("Config synchronization was cancelled by event for: " + serializedName); continue; } val bytes = new byte[dataSize]; diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java index 20581fe..897f71a 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/fields/EnumConfigField.java @@ -22,6 +22,7 @@ package com.falsepattern.lib.internal.impl.config.fields; import com.falsepattern.lib.config.Config; import com.falsepattern.lib.config.event.ConfigValidationFailureEvent; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import com.google.common.collect.BiMap; import com.google.common.collect.HashBiMap; @@ -102,7 +103,7 @@ public class EnumConfigField> extends AConfigField { private T getEnumByName(String name) { if (!enumNameMap.containsKey(name)) { - Share.LOG.warn("Invalid value " + FPLog.LOG.warn("Invalid value " + name + " for enum configuration field " + field.getName() diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java index a3e6285..47e963f 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncReplyHandler.java @@ -20,6 +20,7 @@ */ package com.falsepattern.lib.internal.impl.config.net; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import cpw.mods.fml.common.network.simpleimpl.IMessage; @@ -40,7 +41,7 @@ public class SyncReplyHandler implements IMessageHandler { try { message.receive(); } catch (IOException e) { - Share.LOG.error("Failed to sync config", e); + FPLog.LOG.error("Failed to sync config", e); } return null; } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java index 657e37e..b525f39 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/config/net/SyncRequestHandler.java @@ -20,6 +20,7 @@ */ package com.falsepattern.lib.internal.impl.config.net; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.Share; import lombok.val; @@ -56,7 +57,7 @@ public class SyncRequestHandler implements IMessageHandler downloadSizeCallback) { if (Files.exists(target)) { return; } - Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target))); + Internet.transferAndClose(is, new BufferedOutputStream(Files.newOutputStream(target)), downloadSizeCallback); } public static void loadLibraries(Library... libraries) { @@ -346,7 +359,50 @@ public class DependencyLoaderImpl { + "(?:\\.(?:(?:[0-9]+[a-zA-Z-][\\w-]*)|(?:[a-zA-Z][\\w-]*)|(?:[1-9]\\d*)|0))*))?" + "(?:\\+([\\w-]+(\\.[\\w-]+)*))?"); - public static void scanDeps() { + 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 Pair scopedDependency(DependencyScope scope, DepRoot.SidedDependencies deps) { + return new Pair<>(scope, deps); + } + + private static Stream> sidedDependency(DependencySide side, Stream> deps) { + return deps.map(dep -> new Pair<>(new ScopeSide(dep.a, side), dep.b)); + } + + private static boolean initialScan = false; + private static List> tasks; + + public static void executeDependencyLoading(boolean sideAware) { + if (!initialScan) { + initialScan = true; + scanDeps(); + } + + executeArtifactLoading(sideAware); + } + + private static void scanDeps() { LOG.debug("Discovering dependency source candidates..."); val modsDir = FileUtil.getMinecraftHomePath().resolve("mods"); val mods1710Dir = modsDir.resolve("1.7.10"); @@ -360,7 +416,7 @@ public class DependencyLoaderImpl { LOG.error("Could not read dependency scanner cache", e); } } - val candidates = Stream.of(Launch.classLoader.getSources().stream(), + val candidates = Stream.of(LowLevelCallMultiplexer.getClassPathSources().stream(), grabSourceCandidatesFromFolder(modsDir), grabSourceCandidatesFromFolder(mods1710Dir)) .flatMap((i) -> i) @@ -412,18 +468,20 @@ public class DependencyLoaderImpl { val artifacts = dependencySpecs.stream() .map((root) -> new Pair<>(root.source(), root.dependencies())) .flatMap(pair -> flatMap(pair, - (dep) -> Stream.of(dep.always(), - Share.DEV_ENV ? dep.dev() - : dep.obf()))) + (dep) -> Stream.of(scopedDependency(DependencyScope.ALWAYS, dep.always()), + scopedDependency(DependencyScope.DEV, dep.dev()), + scopedDependency(DependencyScope.OBF, dep.obf()) + ))) .flatMap(pair -> flatMap(pair, - (dep) -> Stream.concat(dep.common().stream(), - FMLLaunchHandler.side() - .isClient() - ? dep.client().stream() - : dep.server().stream()))) + (dep) -> Stream.concat(sidedDependency(DependencySide.COMMON, flatMap(dep, d -> d.common().stream())), + Stream.concat(sidedDependency(DependencySide.CLIENT, flatMap(dep, d -> d.client().stream())), + sidedDependency(DependencySide.SERVER, flatMap(dep, d -> d.server().stream()))) + ))) .map((pair) -> { val source = pair.a; - val dep = pair.b; + val scopedDep = pair.b; + val dep = scopedDep.b; + val scope = scopedDep.a; val parts = dep.split(":"); if (parts.length < 3) { LOG.error("Invalid dependency: {}", dep); @@ -468,43 +526,148 @@ public class DependencyLoaderImpl { version, source); } - return new DependencyLoadTask(source, - groupId, - artifactId, - version, - null, - version, - classifier, - classifier); + return new Pair<>(scope, + new DependencyLoadTask(source, + groupId, + artifactId, + version, + null, + version, + classifier, + classifier)); }) .filter(Objects::nonNull) .collect(Collectors.toSet()); + tasks = new ArrayList<>(artifacts); + } + + private static class SideAwareAssistant { + static ScopeSide current() { + return new ScopeSide(Share.DEV_ENV ? DependencyScope.DEV : DependencyScope.OBF, FMLLaunchHandler.side() == Side.CLIENT ? DependencySide.CLIENT : DependencySide.SERVER); + } + } + + private static void executeArtifactLoading(boolean sideAware) { + val scopeSide = sideAware ? SideAwareAssistant.current() : new ScopeSide(DependencyScope.ALWAYS, DependencySide.COMMON); + val iter = tasks.iterator(); val artifactMap = new HashMap(); - for (val artifact : artifacts) { - val id = artifact.getGroupArtifact(); + 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 (artifact.preferredVersion.compareTo(otherArtifact.preferredVersion) > 0) { + if (artifactPayload.preferredVersion.compareTo(otherArtifact.preferredVersion) > 0) { LOG.info("Replacing dependency {}:{} from {} with version {} from {}", otherArtifact.getGroupArtifact(), otherArtifact.preferredVersion, otherArtifact.loadingModId, - artifact.preferredVersion, - artifact.loadingModId); - artifactMap.put(id, artifact); + artifactPayload.preferredVersion, + artifactPayload.loadingModId); + artifactMap.put(id, artifactPayload); } } else { - artifactMap.put(id, artifact); + artifactMap.put(id, artifactPayload); } } + if (artifactMap.isEmpty()) + return; val futures = new ArrayList>(); - for (val task : artifactMap.values()) { - futures.add(CompletableFuture.runAsync(task::load, executor)); + LOG.info("-----------------------------------------------------------"); + LOG.info("FalsePatternLib is downloading dependencies. Please wait..."); + LOG.info("-----------------------------------------------------------"); + JFrame theFrame; + val progresses = new HashMap(); + { + JFrame jFrame = null; + try { + 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); + jFrame.setVisible(true); + Dimension dim = Toolkit.getDefaultToolkit().getScreenSize(); + jFrame.setLocation(dim.width/2-jFrame.getSize().width/2, dim.height/2-jFrame.getSize().height/2); + } catch (Exception ignored) { + } + theFrame = jFrame; + } + if (theFrame != null) { + for (val task : artifactMap.values()) { + futures.add(CompletableFuture.runAsync(() -> { + val bar = progresses.get(task); + bar.setString("Downloading..."); + task.load(); + bar.setMinimum(0); + bar.setMaximum(1); + bar.setValue(1); + bar.setString("Completed!"); + }, executor)); + } + } else { + for (val task : artifactMap.values()) { + futures.add(CompletableFuture.runAsync(task::load, executor)); + } } val theFuture = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])); + AtomicBoolean doViz = new AtomicBoolean(true); + if (theFrame != null) { + final var vizThread = getVizThread(doViz, progresses, theFrame); + vizThread.start(); + } theFuture.join(); + if (theFrame != null) { + doViz.set(false); + } + } + + @NotNull + private static Thread getVizThread(AtomicBoolean doViz, HashMap progresses, JFrame theFrame) { + val vizThread = new Thread(() -> { + while (doViz.get()) { + 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.setName("FalsePatternLib Download Visualizer"); + return vizThread; } @RequiredArgsConstructor @@ -527,9 +690,12 @@ public class DependencyLoaderImpl { private String artifactLogName; private String artifact; private String mavenJarName; - private String jarName; + public volatile String jarName; private Path file; + public volatile long contentLength = -1; + public volatile long downloaded = 0; + private void load() { try { setupLibraryNames(); @@ -647,7 +813,7 @@ public class DependencyLoaderImpl { } private void validateDownloadsAllowed() { - if (!LibraryConfig.ENABLE_LIBRARY_DOWNLOADS) { + if (!EarlyConfig.load().enableLibraryDownloads()) { val errorMessage = "Failed to load library " + groupId + ":" @@ -684,18 +850,28 @@ public class DependencyLoaderImpl { 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 {}: {}", + ex -> LOG.debug("Artifact {} could not be downloaded from repo {}: {}", artifactLogName, finalRepo, ex.getMessage()), - (input) -> { + input -> { LOG.debug("Downloading {} from {}", artifactLogName, finalRepo); - download(input, file); + download(input, tmpFile, d -> downloaded += d); LOG.debug("Downloaded {} from {}", artifactLogName, finalRepo); success.set(true); - }); + }, + contentLength -> this.contentLength = contentLength); if (success.get()) { + 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) { @@ -732,10 +908,11 @@ public class DependencyLoaderImpl { ex.getMessage()), (input) -> { LOG.debug("Downloading {} checksum for {}", checksumType, artifactLogName); - download(input, checksumFile); + download(input, checksumFile, d -> {}); LOG.debug("Downloaded {} checksum for {}", checksumType, artifactLogName); success.set(true); - }); + }, + length -> {}); if (success.get()) { return getChecksumStatus(file, checksumType, checksumFile); } diff --git a/src/main/java/com/falsepattern/lib/internal/impl/optifine/OptiFineTransformerHooksImpl.java b/src/main/java/com/falsepattern/lib/internal/impl/optifine/OptiFineTransformerHooksImpl.java index 5358a2d..12f52e0 100644 --- a/src/main/java/com/falsepattern/lib/internal/impl/optifine/OptiFineTransformerHooksImpl.java +++ b/src/main/java/com/falsepattern/lib/internal/impl/optifine/OptiFineTransformerHooksImpl.java @@ -21,6 +21,7 @@ package com.falsepattern.lib.internal.impl.optifine; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import lombok.val; @@ -42,7 +43,7 @@ public class OptiFineTransformerHooksImpl { try { tField = LaunchClassLoader.class.getDeclaredField("transformers"); } catch (NoSuchFieldException e) { - Share.LOG.error("Could not retrieve transformers field", e); + FPLog.LOG.error("Could not retrieve transformers field", e); return; } tField.setAccessible(true); @@ -50,14 +51,14 @@ public class OptiFineTransformerHooksImpl { try { transformers = (List)tField.get(Launch.classLoader); } catch (IllegalAccessException e) { - Share.LOG.error("Could not retrieve transformers list", e); + FPLog.LOG.error("Could not retrieve transformers list", e); return; } for (int i = 0; i < transformers.size(); i++) { val transformer = transformers.get(i); System.out.println(transformer.getClass().getName()); if (transformer.getClass().getName().equals("optifine.OptiFineClassTransformer")) { - Share.LOG.info("Attaching OptiFine ASM transformer hooks"); + FPLog.LOG.info("Attaching OptiFine ASM transformer hooks"); transformers.set(i, new WrappedOptiFineClassTransformer(transformer)); } } diff --git a/src/main/java/com/falsepattern/lib/mapping/MappingManager.java b/src/main/java/com/falsepattern/lib/mapping/MappingManager.java index a47d23a..e9fb13b 100644 --- a/src/main/java/com/falsepattern/lib/mapping/MappingManager.java +++ b/src/main/java/com/falsepattern/lib/mapping/MappingManager.java @@ -21,6 +21,7 @@ package com.falsepattern.lib.mapping; import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.FalsePatternLib; import com.falsepattern.lib.internal.Share; import com.falsepattern.lib.internal.asm.CoreLoadingPlugin; @@ -69,7 +70,7 @@ public class MappingManager { if (time - lastInit < 10_000) { continue; } - Share.LOG.info("Terminating MappingManager"); + FPLog.LOG.info("Terminating MappingManager"); internalLookup = null; regularLookup = null; initialized = false; @@ -88,7 +89,7 @@ public class MappingManager { } initialized = true; val cleanupThread = new CleanupThread(); - Share.LOG.info("Initializing MappingManager"); + FPLog.LOG.info("Initializing MappingManager"); internalLookup = new Lookup<>(); regularLookup = new Lookup<>(); val stringPool = new HashMap(); diff --git a/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java b/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java index fefc398..a8868bf 100644 --- a/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java +++ b/src/main/java/com/falsepattern/lib/updates/UpdateChecker.java @@ -22,6 +22,7 @@ package com.falsepattern.lib.updates; import com.falsepattern.lib.DeprecationDetails; import com.falsepattern.lib.StableAPI; +import com.falsepattern.lib.internal.FPLog; import com.falsepattern.lib.internal.Share; import net.minecraft.util.IChatComponent; @@ -44,7 +45,7 @@ public final class UpdateChecker { */ @StableAPI.Expose(since = "0.11.0") public static CompletableFuture> fetchUpdatesAsyncV2(String url) { - Share.deprecatedWarning(new Throwable()); + FPLog.deprecatedWarning(new Throwable()); return CompletableFuture.completedFuture(Collections.emptyList()); } @@ -53,7 +54,7 @@ public final class UpdateChecker { */ @StableAPI.Expose(since = "0.11.0") public static List fetchUpdatesV2(String url) throws UpdateCheckException { - Share.deprecatedWarning(new Throwable()); + FPLog.deprecatedWarning(new Throwable()); return Collections.emptyList(); } @@ -62,7 +63,7 @@ public final class UpdateChecker { */ @StableAPI.Expose public static List updateListToChatMessages(String initiator, List updates) { - Share.deprecatedWarning(new Throwable()); + FPLog.deprecatedWarning(new Throwable()); return Collections.emptyList(); } } diff --git a/src/main/resources/META-INF/rfb-plugin/falsepatternlib.properties b/src/main/resources/META-INF/rfb-plugin/falsepatternlib.properties new file mode 100644 index 0000000..d5455ec --- /dev/null +++ b/src/main/resources/META-INF/rfb-plugin/falsepatternlib.properties @@ -0,0 +1,3 @@ +name=falsepatternlib +version=1.0.0 +className=com.falsepattern.lib.internal.asm.RFBLoadingPlugin \ No newline at end of file