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