improve the robustness of the update checker, plus RFB compat

This commit is contained in:
FalsePattern committed 2024-04-17 17:17:44 +02:00
1 parent b252491fbd
commit 49c319aaef
21 files changed
+507 -137

No files matched your search

@@ -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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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
@@ -45,22 +45,11 @@ import java.util.function.Consumer;
public class Internet {
private static final Map<String, String> NO_HEADERS = Collections.emptyMap();
public static void connect(URL URL, Consumer<Exception> onError, Consumer<InputStream> onSuccess) {
connect(URL, NO_HEADERS, onError, onSuccess);
public static void connect(URL URL, Consumer<Exception> onError, Consumer<InputStream> onSuccess, Consumer<Long> contentLengthCallback) {
connect(URL, NO_HEADERS, onError, onSuccess, contentLengthCallback);
}
public static Map<String, String> constructHeaders(String... entries) {
if (entries.length % 2 != 0) {
throw new IllegalArgumentException("Entries must be in pairs");
}
val map = new java.util.HashMap<String, String>();
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<String, String> headers, Consumer<Exception> onError, Consumer<InputStream> onSuccess) {
public static void connect(URL URL, Map<String, String> headers, Consumer<Exception> onError, Consumer<InputStream> onSuccess, Consumer<Long> 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<byte[]> download(URL... urls) {
return CompletableFuture.supplyAsync(() -> {
val result = new ByteArrayOutputStream();
val caught = new AtomicReference<Exception>();
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<Void> downloadFile(File file, URL... urls) {
return CompletableFuture.runAsync(() -> {
val caught = new AtomicReference<Exception>();
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<Integer> 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();
@@ -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");
@@ -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 <https://www.gnu.org/licenses/>.
*/
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);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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;
}
}
@@ -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;
}
@@ -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."})
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<URL> 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<URL> 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<URL> getClassPathSources() {
return Launch.classLoader.getSources();
}
static Path gameDir() {
return Launch.minecraftHome == null ? Paths.get(".") : Launch.minecraftHome.toPath();
}
}
}
@@ -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];
@@ -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<T extends Enum<T>> extends AConfigField<T> {
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()
@@ -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<SyncReply, IMessage> {
try {
message.receive();
} catch (IOException e) {
Share.LOG.error("Failed to sync config", e);
FPLog.LOG.error("Failed to sync config", e);
}
return null;
}
@@ -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<SyncRequest, IMessage
FalsePatternLib.NETWORK.sendTo(reply, ctx.getServerHandler().playerEntity);
return null;
} catch (IOException e) {
Share.LOG.error("Failed to sync config", e);
FPLog.LOG.error("Failed to sync config", e);
return null;
}
}
@@ -26,7 +26,8 @@ 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.LibraryConfig;
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;
@@ -38,20 +39,32 @@ import lombok.val;
import lombok.var;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import net.minecraft.launchwrapper.Launch;
import net.minecraft.launchwrapper.LaunchClassLoader;
import cpw.mods.fml.relauncher.FMLLaunchHandler;
import cpw.mods.fml.relauncher.Side;
import javax.swing.BoxLayout;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JProgressBar;
import javax.swing.JSeparator;
import javax.swing.SwingConstants;
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.File;
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;
@@ -70,6 +83,7 @@ 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;
@@ -216,8 +230,7 @@ public class DependencyLoaderImpl {
private static synchronized void addToClasspath(Path file) {
try {
val cl = (LaunchClassLoader) DependencyLoaderImpl.class.getClassLoader();
cl.addURL(file.toUri().toURL());
LowLevelCallMultiplexer.addURLToClassPath(file.toUri().toURL());
LOG.debug("Injected file {} into classpath!", file);
} catch (Exception e) {
throw new RuntimeException("Failed to add library to classpath: " + file.toAbsolutePath(), e);
@@ -225,11 +238,11 @@ public class DependencyLoaderImpl {
}
@SneakyThrows
private static void download(InputStream is, Path target) {
private static void download(InputStream is, Path target, Consumer<Integer> 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<DependencyScope, DepRoot.SidedDependencies> scopedDependency(DependencyScope scope, DepRoot.SidedDependencies deps) {
return new Pair<>(scope, deps);
}
private static Stream<Pair<ScopeSide, String>> sidedDependency(DependencySide side, Stream<Pair<DependencyScope, String>> deps) {
return deps.map(dep -> new Pair<>(new ScopeSide(dep.a, side), dep.b));
}
private static boolean initialScan = false;
private static List<Pair<ScopeSide, DependencyLoadTask>> 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<String, DependencyLoadTask>();
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<CompletableFuture<Void>>();
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<DependencyLoadTask, JProgressBar>();
{
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<DependencyLoadTask, JProgressBar> 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);
}
@@ -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<IClassTransformer>)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));
}
}
@@ -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<String, String>();
@@ -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<List<ModUpdateInfo>> 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<ModUpdateInfo> 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<IChatComponent> updateListToChatMessages(String initiator, List<ModUpdateInfo> updates) {
Share.deprecatedWarning(new Throwable());
FPLog.deprecatedWarning(new Throwable());
return Collections.emptyList();
}
}