diff --git a/CHANGELOG.md b/CHANGELOG.md index 020cdacd..ea3c39b7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,33 +2,7 @@ ## Alpha warning! -- [**1.19.3 is no longer supported!**](https://github.com/melontini/dark-matter/wiki#version-support) -- **1.18.2 will no longer receive alpha updates!** - -*** - -[`entities/slimes`]: - -* Added slime particles on collision. - -[`blocks/campfire_effects`]: - -* The game will no longer crash if the campfire configuration is invalid. - -[`items/magnet`]: - -* Magnet level is now capped at 5 even if NBT is modified. - -[`items/lockpick`]: - -* Fixed lockpick sending break status even when not broken. - -[`entities/boats`]: - -* Fixed a possible crash with TNT boats when `horizontalCollision` is updated on server. - [`general`]: -* Improved crash reporting. -* A bunch of other improvements. -* Updated Chinese translation. Courtesy of [Rad586](https://github.com/Rad586). \ No newline at end of file +* Added a workaround to fix mixin config related crashes on Quilt. +* Fixed BCLib crashing GUI particles on 1.20+. \ No newline at end of file diff --git a/src/main/java/me/melontini/andromeda/base/Bootstrap.java b/src/main/java/me/melontini/andromeda/base/Bootstrap.java index 2676862d..a977d748 100644 --- a/src/main/java/me/melontini/andromeda/base/Bootstrap.java +++ b/src/main/java/me/melontini/andromeda/base/Bootstrap.java @@ -77,7 +77,7 @@ public class Bootstrap { for (org.spongepowered.asm.mixin.transformer.Config config : Mixins.getConfigs()) { if (!config.isVisited() && config.getName().startsWith("andromeda_dynamic$$")) throw AndromedaException.builder() - .message("Mixin failed to consume Andromeda's late configs!") + .message("Mixin failed to consume Andromeda's late configs!").message(MixinProcessor.NOTICE) .add("mixin_config", config.getName()) .build(); } @@ -144,7 +144,7 @@ public class Bootstrap { m.print(); //Scan for mixins. m.loaded().forEach(module -> getModuleClassPath().addUrl(module.getClass().getProtectionDomain().getCodeSource().getLocation())); - run(() -> MixinProcessor.addMixins(m), (b) -> b.message("Failed to inject dynamic mixin configs!")); + run(() -> MixinProcessor.addMixins(m), (b) -> b.message("Failed to inject dynamic mixin configs!").message(MixinProcessor.NOTICE)); FabricLoader.getInstance().getObjectShare().put("andromeda:module_manager", m); Status.update(Status.PRE_LAUNCH); diff --git a/src/main/java/me/melontini/andromeda/base/MixinProcessor.java b/src/main/java/me/melontini/andromeda/base/MixinProcessor.java index 01690ea7..a0005f0e 100644 --- a/src/main/java/me/melontini/andromeda/base/MixinProcessor.java +++ b/src/main/java/me/melontini/andromeda/base/MixinProcessor.java @@ -31,6 +31,7 @@ import java.util.Set; @CustomLog public class MixinProcessor { + public static final String NOTICE = "## Mixin configs are internal mod components and are not the same as user configs! ##"; private static final ThreadLocal CONFIG = ThreadLocal.withInitial(() -> null); private static boolean done = false; @@ -48,7 +49,7 @@ public class MixinProcessor { manager.mixinConfigs.put(cfg, module); } catch (IOException e) { throw AndromedaException.builder() - .message("Couldn't inject mixin config for module '%s'".formatted(module.meta().id())) + .message("Couldn't inject mixin config for module '%s'".formatted(module.meta().id())).message(NOTICE) .add("mixin_config", cfg).add("module", module.meta().id()).build(); } finally { CONFIG.remove(); diff --git a/src/main/java/me/melontini/andromeda/base/hacks/EntrypointHack.java b/src/main/java/me/melontini/andromeda/base/hacks/EntrypointHack.java new file mode 100644 index 00000000..7e2f7ab8 --- /dev/null +++ b/src/main/java/me/melontini/andromeda/base/hacks/EntrypointHack.java @@ -0,0 +1,31 @@ +package me.melontini.andromeda.base.hacks; + +import lombok.CustomLog; +import me.melontini.andromeda.util.CommonValues; + +/** + * Some mods like to load MC classes during {@code preLaunch}, which causes Andromeda to crash. + * We need to move our {@code preLaunch} to the top. This may be Quilt specific, but just to be safe, the fix applies to Fabric as well. + *

If this doesn't work, I'll have to resort to {@link org.spongepowered.asm.mixin.transformer.ext.IExtension}. The ultimate entrypoint :)

+ */ +@CustomLog +public class EntrypointHack { + + public static boolean pushPreLaunch() { + try { + return switch (CommonValues.platform()) { + case FABRIC, CONNECTOR -> { + LOGGER.info("Trying Fabric-style entrypoint push!"); + yield new FabricEntrypointHack().pushPreLaunch(); + } + case QUILT -> { + LOGGER.info("Trying Quilt-style entrypoint push!"); + yield new QuiltEntrypointHack().pushPreLaunch(); + } + default -> true; + }; + } catch (Throwable t) { + return false; + } + } +} diff --git a/src/main/java/me/melontini/andromeda/base/hacks/FabricEntrypointHack.java b/src/main/java/me/melontini/andromeda/base/hacks/FabricEntrypointHack.java new file mode 100644 index 00000000..d188c1cd --- /dev/null +++ b/src/main/java/me/melontini/andromeda/base/hacks/FabricEntrypointHack.java @@ -0,0 +1,59 @@ +package me.melontini.andromeda.base.hacks; + +import lombok.CustomLog; +import lombok.SneakyThrows; +import me.melontini.dark_matter.api.base.reflect.wrappers.GenericField; +import me.melontini.dark_matter.api.base.reflect.wrappers.GenericMethod; +import me.melontini.dark_matter.api.base.util.Utilities; +import net.fabricmc.loader.impl.FabricLoaderImpl; +import net.fabricmc.loader.impl.ModContainerImpl; +import net.fabricmc.loader.impl.entrypoint.EntrypointStorage; + +import java.util.Comparator; +import java.util.List; +import java.util.Map; + +@CustomLog +public class FabricEntrypointHack { + + private GenericField esField; + private GenericField>> emField; + + FabricEntrypointHack() { + try { + esField = GenericField.of(FabricLoaderImpl.class, "entrypointStorage"); + emField = GenericField.of(EntrypointStorage.class, "entryMap"); + + esField.accessible(true); + emField.accessible(true); + } catch (Throwable t) { + LOGGER.error("Failed to prepare Fabric-style push!", t); + } + } + + @SneakyThrows + boolean pushPreLaunch() { + if (esField == null || emField == null) { + LOGGER.error("Fabric-style entrypoint push failed! Internal fields changed! :("); + return false; + } + EntrypointStorage storage = esField.get(FabricLoaderImpl.INSTANCE); + var entryMap = emField.get(storage); + + var EntrypointStorage$Entry = Class.forName("net.fabricmc.loader.impl.entrypoint.EntrypointStorage$Entry"); + GenericMethod getModContainer = GenericMethod.of(EntrypointStorage$Entry, "getModContainer"); + entryMap.get("preLaunch").sort(Comparator.comparingInt(value -> { + try { + ModContainerImpl container = getModContainer.invoke(Utilities.cast(value)); + if (container.getMetadata().getId().equals("andromeda")) return 0; + } catch (Throwable t) { + return 1; + } + return 1; + })); + + LOGGER.debug(entryMap.get("preLaunch")); + LOGGER.info("Pushed entrypoint successfully!"); + return true; + } +} diff --git a/src/main/java/me/melontini/andromeda/base/hacks/QuiltEntrypointHack.java b/src/main/java/me/melontini/andromeda/base/hacks/QuiltEntrypointHack.java new file mode 100644 index 00000000..cdea5a63 --- /dev/null +++ b/src/main/java/me/melontini/andromeda/base/hacks/QuiltEntrypointHack.java @@ -0,0 +1,93 @@ +package me.melontini.andromeda.base.hacks; + +import lombok.CustomLog; +import me.melontini.andromeda.base.Bootstrap; +import me.melontini.dark_matter.api.base.reflect.wrappers.GenericField; +import me.melontini.dark_matter.api.base.util.Utilities; + +import java.lang.reflect.Field; +import java.lang.reflect.Proxy; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@CustomLog +public class QuiltEntrypointHack { + + Object loader; + Field esField; + GenericField>> emField; + Class EntrypointStorage$Entry; + Class PreLaunchEntrypoint; + + QuiltEntrypointHack() { + try { + Class QuiltLoaderImpl = Class.forName("org.quiltmc.loader.impl.QuiltLoaderImpl"); + Field INSTANCE = QuiltLoaderImpl.getField("INSTANCE"); + loader = INSTANCE.get(null); + + esField = QuiltLoaderImpl.getDeclaredField("entrypointStorage"); + esField.setAccessible(true); + + Class EntrypointStorage = Class.forName("org.quiltmc.loader.impl.entrypoint.EntrypointStorage"); + EntrypointStorage$Entry = Class.forName("org.quiltmc.loader.impl.entrypoint.EntrypointStorage$Entry"); + PreLaunchEntrypoint = Class.forName("org.quiltmc.loader.api.entrypoint.PreLaunchEntrypoint"); + + emField = GenericField.of(EntrypointStorage, "entryMap"); + emField.accessible(true); + } catch (Throwable t) { + LOGGER.error("Failed to prepare Quilt-style push!", t); + } + } + + boolean pushPreLaunch() { + if (loader == null || esField == null || emField == null) { + LOGGER.error("Quilt-style entrypoint push failed! Internals changed! :("); + return false; + } + + try { + var realEs = esField.get(loader); + + var entryMap = emField.get(Utilities.cast(realEs)); + + var itr = entryMap.get("preLaunch").iterator(); + while (itr.hasNext()) { + var value = itr.next(); + + if (value.toString().startsWith("andromeda->")) { + itr.remove(); var invoker = invoker(value); + + //We have to use proxies, since Quilt adds a new param. + entryMap.computeIfAbsent("pre_launch", string -> new ArrayList<>()).add(0, + Proxy.newProxyInstance(loader.getClass().getClassLoader(), new Class[]{ EntrypointStorage$Entry }, (proxy, method, args) -> { + if ("getOrCreate".equals(method.getName())) { + return invoker; + } + method.setAccessible(true); + return method.invoke(value, args); + })); + break; + } + } + + LOGGER.debug(entryMap.get("preLaunch")); + LOGGER.info("Pushed entrypoint successfully!"); + } catch (Exception e) { + LOGGER.error("Quilt-style entrypoint push failed!", e); + return false; + } + return true; + } + + private Object invoker(Object value) { + return Proxy.newProxyInstance(loader.getClass().getClassLoader(), new Class[]{ PreLaunchEntrypoint }, (proxy, method, args) -> { + if ("onPreLaunch".equals(method.getName())) { + Bootstrap.onPreLaunch(); + return null; + } + method.setAccessible(true); + return method.invoke(value, args); + }); + } +} diff --git a/src/main/java/me/melontini/andromeda/util/exceptions/AndromedaException.java b/src/main/java/me/melontini/andromeda/util/exceptions/AndromedaException.java index 481cff55..40422560 100644 --- a/src/main/java/me/melontini/andromeda/util/exceptions/AndromedaException.java +++ b/src/main/java/me/melontini/andromeda/util/exceptions/AndromedaException.java @@ -8,6 +8,7 @@ import me.melontini.dark_matter.api.base.util.classes.Context; import me.melontini.dark_matter.api.base.util.classes.ThrowingRunnable; import me.melontini.dark_matter.api.crash_handler.Crashlytics; import me.melontini.dark_matter.api.crash_handler.Prop; +import org.apache.commons.lang3.StringUtils; import java.util.*; import java.util.function.Consumer; @@ -70,7 +71,7 @@ public class AndromedaException extends RuntimeException { return prop.name().toLowerCase(); } - private String message; + private final List message = new ArrayList<>(); private Throwable cause; private boolean report = true; @@ -83,7 +84,7 @@ public class AndromedaException extends RuntimeException { } public Builder message(String message) { - this.message = message; + this.message.add(message); return this; } @@ -123,7 +124,7 @@ public class AndromedaException extends RuntimeException { disableInHierarchy(cause); var e = new AndromedaException(report, - Strings.isNullOrEmpty(message) ? "Something went very wrong!" : message, + message.isEmpty() ? "Something went very wrong!" : StringUtils.join(message.toArray(), '\n'), cause, statuses); //CrashHandler can't automatically handle preLaunch errors, so this is what we have to do. diff --git a/src/main/java/me/melontini/andromeda/util/mixin/AndromedaMixinPlugin.java b/src/main/java/me/melontini/andromeda/util/mixin/AndromedaMixinPlugin.java index 48ddded8..4109a45d 100644 --- a/src/main/java/me/melontini/andromeda/util/mixin/AndromedaMixinPlugin.java +++ b/src/main/java/me/melontini/andromeda/util/mixin/AndromedaMixinPlugin.java @@ -1,6 +1,7 @@ package me.melontini.andromeda.util.mixin; import lombok.CustomLog; +import me.melontini.andromeda.base.hacks.EntrypointHack; import me.melontini.andromeda.util.Debug; import me.melontini.dark_matter.api.base.util.mixin.ExtendablePlugin; import org.spongepowered.asm.mixin.Mixins; @@ -16,6 +17,10 @@ public class AndromedaMixinPlugin extends ExtendablePlugin { @Override public void onPluginLoad(String mixinPackage) { this.mixinPackage = mixinPackage; + if (!EntrypointHack.pushPreLaunch()) { + //TODO: try some workaround? explain why this is bad? + } + AndromedaMixins.getClassPath().addUrl(this.getClass().getProtectionDomain().getCodeSource().getLocation()); Debug.load(); -- 2.51.2 From cce16ce8d0e6895ed6cc21b1cf4b2969b391d866 Mon Sep 17 00:00:00 2001 From: melontini <104443436+melontini@users.noreply.github.com> Date: Sun, 7 Jan 2024 16:52:06 +0700 Subject: [PATCH 2/2] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 5fe4daa4..3c59518d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,7 +8,7 @@ minecraft_version=1.19.2 yarn_mappings=1.19.2+build.28 loader_version=0.15.0 # Mod Properties -mod_version=1.4.3-1.19.2 +mod_version=1.4.4-1.19.2 maven_group=me.melontini archives_base_name=andromeda # Dependencies