diff options
Diffstat (limited to 'src/main/java/cc/polyfrost/oneconfig')
7 files changed, 330 insertions, 15 deletions
diff --git a/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java b/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java index 61517ab..f77639f 100644 --- a/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java +++ b/src/main/java/cc/polyfrost/oneconfig/config/data/Mod.java @@ -29,15 +29,18 @@ package cc.polyfrost.oneconfig.config.data; import cc.polyfrost.oneconfig.config.Config; import cc.polyfrost.oneconfig.config.elements.OptionPage; import cc.polyfrost.oneconfig.config.migration.Migrator; -import cc.polyfrost.oneconfig.internal.config.OneConfigConfig; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; public class Mod implements Comparable<Mod> { + @NotNull public final String name; public final ModType modType; + @Nullable public final String modIcon; + @Nullable public final Migrator migrator; + @NotNull public final OptionPage defaultPage; public Config config; @@ -47,7 +50,7 @@ public class Mod implements Comparable<Mod> { * @param modIcon Path to icon of the mod (png or svg format) * @param migrator Migrator class to port the old config */ - public Mod(String name, ModType modType, @Nullable String modIcon, @Nullable Migrator migrator) { + public Mod(@NotNull String name, ModType modType, @Nullable String modIcon, @Nullable Migrator migrator) { this.name = name; this.modType = modType; this.modIcon = modIcon; diff --git a/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java b/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java index bc0e3a9..874abd2 100644 --- a/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java +++ b/src/main/java/cc/polyfrost/oneconfig/internal/config/Preferences.java @@ -35,10 +35,17 @@ import cc.polyfrost.oneconfig.platform.Platform; import cc.polyfrost.oneconfig.utils.TickDelay; public class Preferences extends InternalConfig { + + @Dropdown( + name = "Release Channel", + options = {"Releases", "Pre-Releases"} + ) + public static int updateChannel = 0; + @Switch( - name = "Enable Blur" + name = "Debug Mode" ) - public static boolean enableBlur = true; + public static boolean DEBUG = false; @KeyBind( name = "OneConfig Keybind", @@ -46,23 +53,21 @@ public class Preferences extends InternalConfig { ) public static OneKeyBind oneConfigKeyBind = new OneKeyBind(UKeyboard.KEY_RSHIFT); - @Dropdown( - name = "Release Channel", - options = {"Releases", "Pre-Releases"}, - size = 2 + @Switch( + name = "Enable Blur", + subcategory = "GUI Settings" ) - public static int updateChannel = 0; + public static boolean enableBlur = true; @Switch( name = "Use custom GUI scale", - subcategory = "GUI Scale", - size = 2 + subcategory = "GUI Settings" ) public static boolean enableCustomScale = false; @Slider( name = "Custom GUI scale", - subcategory = "GUI Scale", + subcategory = "GUI Settings", min = 0.5f, max = 5f ) diff --git a/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java b/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java new file mode 100644 index 0000000..e8d048c --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/internal/utils/Deprecator.java @@ -0,0 +1,64 @@ +/* + * This file is part of OneConfig. + * OneConfig - Next Generation Config Library for Minecraft: Java Edition + * Copyright (C) 2021, 2022 Polyfrost. + * <https://polyfrost.cc> <https://github.com/Polyfrost/> + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * OneConfig is licensed under the terms of version 3 of the GNU Lesser + * General Public License as published by the Free Software Foundation, AND + * under the Additional Terms Applicable to OneConfig, as published by Polyfrost, + * either version 1.0 of the Additional Terms, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License. If not, see <https://www.gnu.org/licenses/>. You should + * have also received a copy of the Additional Terms Applicable + * to OneConfig, as published by Polyfrost. If not, see + * <https://polyfrost.cc/legal/oneconfig/additional-terms> + */ + +package cc.polyfrost.oneconfig.internal.utils; + +import cc.polyfrost.oneconfig.utils.LogScanner; +import cc.polyfrost.oneconfig.utils.Notifications; + +import java.util.ArrayList; +import java.util.List; + +/** + * Class used by OneConfig for deprecation related utilities. + */ +public final class Deprecator { + private static final List<String> warned = new ArrayList<>(); + + /** + * mark a method as deprecated. When a method has this call, it will + * throw a new exception to grab the name (or package) of the mod that called said method. <br> + * This will then send a notification detailing this to the user, and throw an UnsupportedOperationException to print a stack to the log. + */ + public static void markDeprecated() { + try { + throw new Exception("This method is deprecated"); + } catch (Exception e) { + String culprit = LogScanner.identifyCallerFromStacktrace(e).stream().map(activeMod -> activeMod.name).findFirst().orElse("Unknown"); + // sometimes it blames OneConfig as well so + if(culprit.equals("OneConfig")) return; + if (!warned.contains(culprit)) { + warned.add(culprit); + Notifications.INSTANCE.send("Deprecation Warning", "The mod '" + culprit + "' is using a deprecated method, and will no longer work in the future. Please report this to the mod author."); + try { + throw new UnsupportedOperationException("Method " + e.getStackTrace()[1].getClassName() + "." + e.getStackTrace()[1].getMethodName() + "() is deprecated; but is still being used by mod " + culprit + "!"); + } catch (UnsupportedOperationException e1) { + e1.printStackTrace(); + } + } + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java b/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java index b271602..a9fc24e 100644 --- a/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java +++ b/src/main/java/cc/polyfrost/oneconfig/platform/LoaderPlatform.java @@ -26,20 +26,38 @@ package cc.polyfrost.oneconfig.platform; +import cc.polyfrost.oneconfig.libs.checker.nullness.qual.Nullable; +import org.jetbrains.annotations.NotNull; + +import java.io.File; +import java.util.List; + public interface LoaderPlatform { boolean isModLoaded(String id); + boolean hasActiveModContainer(); - ActiveMod getActiveModContainer(); + + @Nullable ActiveMod getActiveModContainer(); + + @Nullable ActiveMod toActiveMod(@Nullable Object in); + + /** + * Note: the list may contain null elements + */ + @NotNull + List<ActiveMod> getLoadedMods(); class ActiveMod { public final String name; public final String id; public final String version; + public final File source; - public ActiveMod(String name, String id, String version) { + public ActiveMod(String name, String id, String version, File source) { this.name = name; this.id = id; this.version = version; + this.source = source; } } } diff --git a/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java b/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java index 1298214..b4cbd28 100644 --- a/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java +++ b/src/main/java/cc/polyfrost/oneconfig/platform/Platform.java @@ -30,7 +30,7 @@ import java.util.ServiceLoader; /** * Contains various platform-specific utilities for OneConfig. - * + * <p> * This is meant for internal usage, however other mods may use these (unless otherwise stated). */ public interface Platform { @@ -71,12 +71,15 @@ public interface Platform { int getMinecraftVersion(); + boolean isDevelopmentEnvironment(); + Loader getLoader(); class PlatformHolder { private PlatformHolder() { } + static PlatformHolder INSTANCE = new PlatformHolder(); Platform platform = ServiceLoader.load(Platform.class, Platform.class.getClassLoader()).iterator().next(); MousePlatform mousePlatform = ServiceLoader.load(MousePlatform.class, MousePlatform.class.getClassLoader()).iterator().next(); diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java b/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java new file mode 100644 index 0000000..5b448a1 --- /dev/null +++ b/src/main/java/cc/polyfrost/oneconfig/utils/LogScanner.java @@ -0,0 +1,219 @@ +/* + * This file is part of OneConfig. + * OneConfig - Next Generation Config Library for Minecraft: Java Edition + * Copyright (C) 2021, 2022 Polyfrost. + * <https://polyfrost.cc> <https://github.com/Polyfrost/> + * + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * OneConfig is licensed under the terms of version 3 of the GNU Lesser + * General Public License as published by the Free Software Foundation, AND + * under the Additional Terms Applicable to OneConfig, as published by Polyfrost, + * either version 1.0 of the Additional Terms, 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 + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License. If not, see <https://www.gnu.org/licenses/>. You should + * have also received a copy of the Additional Terms Applicable + * to OneConfig, as published by Polyfrost. If not, see + * <https://polyfrost.cc/legal/oneconfig/additional-terms> + */ + +/* COPYRIGHT NOTICE: MIT License + * Copyright (c) 2021 Fudge and NEC contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package cc.polyfrost.oneconfig.utils; + +import cc.polyfrost.oneconfig.internal.config.Preferences; +import cc.polyfrost.oneconfig.platform.LoaderPlatform; +import cc.polyfrost.oneconfig.platform.Platform; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.jetbrains.annotations.NotNull; + +import java.net.MalformedURLException; +import java.net.URISyntaxException; +import java.net.URL; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.CodeSource; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +/** + * Adapted from <a href="https://github.com/natanfudge/Not-Enough-Crashes">NotEnoughCrashes</a> under the <a href="https://opensource.org/licenses/MIT">MIT License</a> + */ +public class LogScanner { + static final Logger LOGGER = LogManager.getLogger("OneConfig Log Scanner"); + + /** + * Return a set of ActiveMods that have been blamed for the given stacktrace. + * This will be an empty set if no mods are blamed. + */ + @NotNull + public static Set<LoaderPlatform.ActiveMod> identifyFromStacktrace(Throwable e) { + Set<LoaderPlatform.ActiveMod> mods = new HashSet<>(); + // Include suppressed exceptions too + visitChildrenThrowables(e, throwable -> { + for (LoaderPlatform.ActiveMod newMod : identifyFromThrowable(throwable)) { + if (mods.stream().noneMatch(mod -> mod.id.equals(newMod.id))) { + mods.add(newMod); + } + } + }); + return mods; + } + + /** + * Attempt to get the caller from a given stacktrace. + * + * @return A singleton mod set that contains the caller, or an empty set if no caller is found. + */ + @NotNull + public static Set<LoaderPlatform.ActiveMod> identifyCallerFromStacktrace(Throwable e) { + // first is this method name, second is the method it called, third is what called it + StackTraceElement target = null; + int i = 0; + for (StackTraceElement element : e.getStackTrace()) { + // ignore the first two + if (i > 2) { + // remove any that are native, or called from a system package + if (!element.isNativeMethod() && !element.getClassName().startsWith("sun.") && !element.getClassName().startsWith("java.") + && !element.getClassName().startsWith("javax.") && !element.getClassName().startsWith("jdk.") && !element.getClassName().startsWith("com.sun.")) { + target = element; + break; + } + } + i++; + } + if (target == null) { + return Collections.emptySet(); + } + Set<LoaderPlatform.ActiveMod> classMods = identifyFromClass(target.getClassName()); + return classMods.isEmpty() ? Collections.emptySet() : classMods; + } + + private static void visitChildrenThrowables(Throwable e, Consumer<Throwable> visitor) { + visitor.accept(e); + for (Throwable child : e.getSuppressed()) visitChildrenThrowables(child, visitor); + } + + private static Set<LoaderPlatform.ActiveMod> identifyFromThrowable(Throwable e) { + Set<String> involvedClasses = new LinkedHashSet<>(); + while (e != null) { + for (StackTraceElement element : e.getStackTrace()) { + involvedClasses.add(element.getClassName()); + } + e = e.getCause(); + } + + Set<LoaderPlatform.ActiveMod> mods = new LinkedHashSet<>(); + for (String className : involvedClasses) { + Set<LoaderPlatform.ActiveMod> classMods = identifyFromClass(className); + mods.addAll(classMods); + } + return mods; + } + + private static void debug(Supplier<String> message) { + if (Preferences.DEBUG) LOGGER.info(message.get()); + } + + // TODO: get a list of mixin transformers that affected the class and blame those too + + /** + * Return a set of ActiveMods that have been associated with the given class. + */ + @NotNull + public static Set<LoaderPlatform.ActiveMod> identifyFromClass(String className) { + List<LoaderPlatform.ActiveMod> modMap = Platform.getLoaderPlatform().getLoadedMods(); + // Skip identification for Mixin, one's mod copy of the library is shared with all other mods + if (className.startsWith("org.spongepowered.asm.mixin.")) { + debug(() -> "Ignoring class " + className + " for identification because it is a mixin class"); + return Collections.emptySet(); + } + + try { + // Get the URL of the class (don't initialize classes, though) + Class<?> clazz = Class.forName(className, false, LogScanner.class.getClassLoader()); + CodeSource codeSource = clazz.getProtectionDomain().getCodeSource(); + if (codeSource == null) { + debug(() -> "Ignoring class " + className + " for identification because the code source could not be found"); + return Collections.emptySet(); // Some internal native sun classes + } + URL url = codeSource.getLocation(); + + if (url == null) { + LOGGER.warn("Failed to identify mod for " + className); + return Collections.emptySet(); + } + + // Transform JAR URL to a file URL + if (url.toURI().toString().startsWith("jar:")) { + url = new URL(url.toURI().toString().substring(4, url.toURI().toString().lastIndexOf("!"))); + } + if (url.toURI().toString().endsWith(".class") && Platform.getInstance().isDevelopmentEnvironment()) { + LOGGER.error("The mod you are currently developing caused this issue, or another class file. Returning 'this'."); + LOGGER.error("Class: " + className); + return Collections.singleton(new LoaderPlatform.ActiveMod("this", "this", "Unknown", null)); + } + Set<LoaderPlatform.ActiveMod> mods = getModsAt(Paths.get(url.toURI()), modMap); + if (!mods.isEmpty()) { + //noinspection OptionalGetWithoutIsPresent + debug(() -> "Successfully placed blame of '" + className + "' on '" + + mods.stream().findFirst().get().name + "'"); + } + return mods; + } catch (URISyntaxException | ClassNotFoundException | NoClassDefFoundError | MalformedURLException e) { + debug(() -> "Ignoring class " + className + " for identification because an error occurred"); + return Collections.emptySet(); // we cannot do it + } + } + + @NotNull + private static Set<LoaderPlatform.ActiveMod> getModsAt(Path path, List<LoaderPlatform.ActiveMod> modMap) { + Set<LoaderPlatform.ActiveMod> mods = modMap.stream().filter(m -> m.source.toPath().equals(path)).collect(Collectors.toSet()); + if (!mods.isEmpty()) return mods; + else if (Platform.getInstance().isDevelopmentEnvironment()) { + // For some reason, in dev, the mod being tested has the 'resources' folder as the origin instead of the 'classes' folder. + String resourcesPathString = path.toString().replace("\\", "/") + // Make it work with Architectury as well + .replace("common/build/classes/java/main", "fabric/build/resources/main") + .replace("common/build/classes/kotlin/main", "fabric/build/resources/main") + .replace("classes/java/main", "resources/main") + .replace("classes/kotlin/main", "resources/main"); + Path resourcesPath = Paths.get(resourcesPathString); + return modMap.stream().filter(m -> m.source.toPath().equals(resourcesPath)).collect(Collectors.toSet()); + } else { + debug(() -> "Mod at path '" + path.toAbsolutePath() + "' is at fault," + + " but it could not be found in the map of mod paths: " /*+ modMap*/); + return Collections.emptySet(); + } + } +} diff --git a/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java b/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java index bc6e9c0..72b5527 100644 --- a/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java +++ b/src/main/java/cc/polyfrost/oneconfig/utils/TextUtils.java @@ -26,6 +26,7 @@ package cc.polyfrost.oneconfig.utils; +import cc.polyfrost.oneconfig.internal.utils.Deprecator; import cc.polyfrost.oneconfig.renderer.RenderManager; import cc.polyfrost.oneconfig.renderer.font.Font; @@ -47,7 +48,9 @@ public final class TextUtils { * @param font The font to use. * @return The array of lines. */ + @Deprecated public static ArrayList<String> wrapText(long vg, String text, float maxWidth, float fontSize, Font font) { + Deprecator.markDeprecated(); ArrayList<String> wrappedText = new ArrayList<>(); text += " "; int prevIndex = 0; |