From 327c3cd9ff9b938b1d272077a232c43a94fe26f2 Mon Sep 17 00:00:00 2001
From: Evoloxi <69534332+Evoloxi@users.noreply.github.com>
Date: Tue, 14 Apr 2026 02:32:01 +0200
Subject: [PATCH 1/4] Fall back to interfaces when resolving capability fields
(#643)
---
...CapabilityProviderDispatcherGenerator.java | 27 +++++++++++++++++--
1 file changed, 25 insertions(+), 2 deletions(-)
diff --git a/src/main/java/org/embeddedt/modernfix/forge/capability/CapabilityProviderDispatcherGenerator.java b/src/main/java/org/embeddedt/modernfix/forge/capability/CapabilityProviderDispatcherGenerator.java
index 6191a2d7..6aa4fb75 100644
--- a/src/main/java/org/embeddedt/modernfix/forge/capability/CapabilityProviderDispatcherGenerator.java
+++ b/src/main/java/org/embeddedt/modernfix/forge/capability/CapabilityProviderDispatcherGenerator.java
@@ -7,6 +7,7 @@ import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.forge.capability.analysis.CapabilityAnalysisResult;
import org.embeddedt.modernfix.forge.capability.analysis.CapabilityAnalyzer;
import org.embeddedt.modernfix.forge.capability.analysis.CapabilityRef;
+import org.jetbrains.annotations.NotNull;
import org.objectweb.asm.*;
import org.objectweb.asm.commons.GeneratorAdapter;
import org.objectweb.asm.commons.Method;
@@ -190,6 +191,8 @@ public class CapabilityProviderDispatcherGenerator {
* Resolves the actual {@link Capability} instances for all refs at class-generation time.
* Uses reflection (with {@code setAccessible}) so private fields are handled without any
* reflection bytecode appearing in the generated class.
+ *
+ * Field lookup is delegated to {@link #getRefField(Class, CapabilityRef)}
*/
private static List> resolveCapabilityValues(LinkedHashMap capRefIndices) {
@SuppressWarnings("unchecked")
@@ -199,8 +202,7 @@ public class CapabilityProviderDispatcherGenerator {
try {
Class> clazz = Class.forName(ref.owner().replace('/', '.'), false,
CapabilityProviderDispatcherGenerator.class.getClassLoader());
- Field field = clazz.getDeclaredField(ref.fieldName());
- field.setAccessible(true);
+ Field field = getRefField(clazz, ref);
caps[entry.getValue()] = (Capability>) field.get(null);
} catch (ReflectiveOperationException e) {
throw new RuntimeException("Failed to resolve capability field " + ref, e);
@@ -209,6 +211,27 @@ public class CapabilityProviderDispatcherGenerator {
return Arrays.asList(caps);
}
+ /**
+ * Resolves the {@link Field} for the given {@link CapabilityRef},
+ * falls back to the implemented interfaces if no match is found.
+ */
+ private static @NotNull Field getRefField(Class> clazz, CapabilityRef ref) throws NoSuchFieldException {
+ Field field = null;
+ try {
+ field = clazz.getDeclaredField(ref.fieldName());
+ } catch (NoSuchFieldException ignored) {
+ for (Class> iface : clazz.getInterfaces()) {
+ try {
+ field = iface.getDeclaredField(ref.fieldName());
+ break;
+ } catch (NoSuchFieldException ignored1) {}
+ }
+ }
+ if (field == null) throw new NoSuchFieldException(ref.fieldName());
+ field.setAccessible(true);
+ return field;
+ }
+
/**
* Build the dispatch list describing how each provider should be handled.
*/
From c2f585da9551d925c01b391ddd151e02c5037382 Mon Sep 17 00:00:00 2001
From: embeddedt <42941056+embeddedt@users.noreply.github.com>
Date: Tue, 14 Apr 2026 22:20:53 -0400
Subject: [PATCH 2/4] Fix rare crash from HandshakeHandler in 5.27.0+
The existing Forge logic can concurrently modify sentMessages from two threads,
since handleIndexedMessage runs on the Netty thread, while tickServer is on the
server thread. Ticking the handler faster made the race condition significantly
more likely to manifest.
---
.../fix_handshake_stall/HandshakeHandlerMixin.java | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_handshake_stall/HandshakeHandlerMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_handshake_stall/HandshakeHandlerMixin.java
index a3c8f503..ef2b13be 100644
--- a/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_handshake_stall/HandshakeHandlerMixin.java
+++ b/src/main/java/org/embeddedt/modernfix/common/mixin/perf/fix_handshake_stall/HandshakeHandlerMixin.java
@@ -8,8 +8,11 @@ import net.minecraftforge.network.NetworkRegistry;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At;
+import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.Slice;
+import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
+import java.util.Collections;
import java.util.List;
@Mixin(value = HandshakeHandler.class, remap = false)
@@ -23,6 +26,16 @@ public class HandshakeHandlerMixin {
@Shadow
private List sentMessages;
+ /**
+ * @author embeddedt
+ * @reason we must synchronize sentMessages because it is modified from both the Netty thread and the
+ * server thread
+ */
+ @Inject(method = "", at = @At("RETURN"))
+ private void synchronizeSentMessages(CallbackInfo ci) {
+ this.sentMessages = Collections.synchronizedList(this.sentMessages);
+ }
+
/**
* @author embeddedt
* @reason Forge only sends one login payload per tick. It takes many seconds to send all the payloads at this rate.
From 4d2f0da1fce628be7a1af35ab5fb4b3c496596e1 Mon Sep 17 00:00:00 2001
From: Mustafa
Date: Wed, 22 Apr 2026 12:40:07 +0300
Subject: [PATCH 3/4] Reduce log level of mixin.perf.spam_thread_dump to info
Closes #647
---
.../org/embeddedt/modernfix/core/ModernFixMixinPlugin.java | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java
index c8575355..c6a52cf1 100644
--- a/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java
+++ b/src/main/java/org/embeddedt/modernfix/core/ModernFixMixinPlugin.java
@@ -78,8 +78,8 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
while(true) {
try {
Thread.sleep(60000);
- logger.error("------ DEBUG THREAD DUMP (occurs every 60 seconds) ------");
- logger.error(ThreadDumper.obtainThreadDump());
+ logger.info("------ DEBUG THREAD DUMP (occurs every 60 seconds) ------");
+ logger.info(ThreadDumper.obtainThreadDump());
} catch(InterruptedException | RuntimeException e) {}
}
}
@@ -300,4 +300,4 @@ public class ModernFixMixinPlugin implements IMixinConfigPlugin {
}
});
}
-}
\ No newline at end of file
+}
From 26bd7116a1800f09c33e203c46e7fd3894e5f42b Mon Sep 17 00:00:00 2001
From: Mustafa
Date: Wed, 22 Apr 2026 12:50:34 +0300
Subject: [PATCH 4/4] Change log level from warn to debug for successfully
created missing block entities
Closes #648
---
.../mixin/bugfix/missing_block_entities/LevelChunkMixin.java | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java
index 923ca57c..b3aa94d5 100644
--- a/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java
+++ b/src/main/java/org/embeddedt/modernfix/common/mixin/bugfix/missing_block_entities/LevelChunkMixin.java
@@ -88,7 +88,9 @@ public abstract class LevelChunkMixin extends ChunkAccess {
BlockEntity blockEntity = this.getBlockEntity(pos.immutable(), LevelChunk.EntityCreationType.IMMEDIATE);
String blockName = state.getBlock().toString();
if (blockEntity != null) {
- ModernFix.LOGGER.warn("Created missing block entity for {} at {}", blockName, pos.toShortString());
+ if (ModernFix.LOGGER.isDebugEnabled()) {
+ ModernFix.LOGGER.debug("Created missing block entity for {} at {}", blockName, pos.toShortString());
+ }
} else {
ModernFix.LOGGER.error("Block entity is missing for {} at {}, but could not be created", blockName, pos.toShortString());
}