diff --git a/build.gradle b/build.gradle index d8e03ed8..af351100 100644 --- a/build.gradle +++ b/build.gradle @@ -88,6 +88,9 @@ dependencies { // compile against the JEI API but do not include it at runtime modCompileOnly("mezz.jei:jei-${minecraft_version}-forge:${jei_version}") + modRuntimeOnly("curse.maven:jei-238222:4405346") + + modCompileOnly("curse.maven:jeresources-240630:3951643") } tasks.withType(JavaCompile) { @@ -153,7 +156,7 @@ curseforge { id = "790626" changelog = '[Changelog is not currently available]' changelogType = "markdown" - releaseType = "release" + releaseType = "beta" addGameVersion "Forge" addGameVersion minecraft_version mainArtifact remapJar diff --git a/src/main/java/org/embeddedt/modernfix/ModernFix.java b/src/main/java/org/embeddedt/modernfix/ModernFix.java index 6e0d7274..2af32f35 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFix.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFix.java @@ -4,18 +4,20 @@ import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.eventbus.api.SubscribeEvent; -import net.minecraftforge.fml.DistExecutor; -import net.minecraftforge.fml.IExtensionPoint; -import net.minecraftforge.fml.ModList; -import net.minecraftforge.fml.ModLoadingContext; +import net.minecraftforge.fml.*; import net.minecraftforge.fml.common.Mod; import net.minecraftforge.fml.config.ModConfig; +import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent; +import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.loading.FMLLoader; import net.minecraftforge.network.NetworkConstants; import org.apache.commons.lang3.tuple.Pair; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.embeddedt.modernfix.core.config.ModernFixConfig; +import org.embeddedt.modernfix.entity.EntityDataIDSyncHandler; +import org.embeddedt.modernfix.packet.PacketHandler; + import java.lang.management.ManagementFactory; import java.util.concurrent.CountDownLatch; @@ -62,9 +64,30 @@ public class ModernFix { INSTANCE = this; // Register ourselves for server and other game events we are interested in MinecraftForge.EVENT_BUS.register(this); + FMLJavaModLoadingContext.get().getModEventBus().addListener(this::commonSetup); DistExecutor.unsafeRunWhenOn(Dist.CLIENT, () -> () -> MinecraftForge.EVENT_BUS.register(new ModernFixClient())); ModLoadingContext.get().registerExtensionPoint(IExtensionPoint.DisplayTest.class, () -> new IExtensionPoint.DisplayTest(() -> NetworkConstants.IGNORESERVERONLY, (a, b) -> true)); ModLoadingContext.get().registerConfig(ModConfig.Type.COMMON, ModernFixConfig.COMMON_CONFIG); + + MinecraftForge.EVENT_BUS.register(EntityDataIDSyncHandler.class); + PacketHandler.register(); + } + + private static boolean dfuModPresent() { + for(String modId : new String[] { "lazydfu", "datafixerslayer" }) { + if(ModList.get().isLoaded(modId)) + return true; + } + return false; + } + + @SubscribeEvent + public void commonSetup(FMLCommonSetupEvent event) { + if(!dfuModPresent()) { + event.enqueueWork(() -> { + ModLoader.get().addWarning(new ModLoadingWarning(ModLoadingContext.get().getActiveContainer().getModInfo(), ModLoadingStage.COMMON_SETUP, "modernfix.no_lazydfu")); + }); + } } @SubscribeEvent diff --git a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java index 05286676..e71a6274 100644 --- a/src/main/java/org/embeddedt/modernfix/ModernFixClient.java +++ b/src/main/java/org/embeddedt/modernfix/ModernFixClient.java @@ -1,22 +1,32 @@ package org.embeddedt.modernfix; +import com.mojang.datafixers.util.Pair; +import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap; import net.minecraft.client.Minecraft; import net.minecraft.client.gui.screens.ConnectScreen; import net.minecraft.client.gui.screens.TitleScreen; import net.minecraftforge.client.event.CustomizeGuiOverlayEvent; import net.minecraftforge.client.event.ScreenEvent; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.TickEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; +import net.minecraftforge.network.NetworkEvent; import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.load.LoadEvents; +import org.embeddedt.modernfix.packet.EntityIDSyncPacket; import org.embeddedt.modernfix.screen.DeferredLevelLoadingScreen; import java.lang.management.ManagementFactory; -import java.util.Optional; +import java.lang.reflect.Field; +import java.sql.Ref; +import java.util.*; +import java.util.function.Supplier; public class ModernFixClient { public static long worldLoadStartTime; @@ -71,4 +81,106 @@ public class ModernFixClient { event.getLeft().add(brandingString); } } + + /** + * Check if the IDs match and remap them if not. + * @return true if ID remap was needed + */ + private static boolean compareAndSwitchIds(Class eClass, String fieldName, EntityDataAccessor accessor, int newId) { + if(accessor.id != newId) { + ModernFix.LOGGER.warn("Corrected ID mismatch on {} field {}. Client had {} but server wants {}.", + eClass, + fieldName, + accessor.id, + newId); + accessor.id = newId; + return true; + } else { + ModernFix.LOGGER.debug("{} {} ID fine: {}", eClass, fieldName, newId); + return false; + } + } + + /** + * Horrendous hack to allow tracking every synced entity data manager. + * + * This is to ensure we can perform ID fixup on already constructed managers. + */ + public static final Set allEntityDatas = Collections.newSetFromMap(new WeakHashMap<>()); + + private static final Field entriesArrayField; + static { + Field field; + try { + field = SynchedEntityData.class.getDeclaredField("entriesArray"); + field.setAccessible(true); + } catch(ReflectiveOperationException e) { + field = null; + } + entriesArrayField = field; + } + + /** + * Extremely hacky method to detect and correct mismatched entity data parameter IDs on the client and server. + * + * The technique is far from ideal, but it should detect reliably and also not break already constructed entities. + */ + public static void handleEntityIDSync(EntityIDSyncPacket packet, Supplier context) { + Map, List>> info = packet.getFieldInfo(); + context.get().enqueueWork(() -> { + boolean fixNeeded = false; + for(Map.Entry, List>> entry : info.entrySet()) { + Class eClass = entry.getKey(); + for(Pair field : entry.getValue()) { + String fieldName = field.getFirst(); + int newId = field.getSecond(); + try { + Field f = eClass.getDeclaredField(fieldName); + f.setAccessible(true); + EntityDataAccessor accessor = (EntityDataAccessor)f.get(null); + if(compareAndSwitchIds(eClass, fieldName, accessor, newId)) + fixNeeded = true; + } catch(NoSuchFieldException e) { + ModernFix.LOGGER.warn("Couldn't find field on {}: {}", eClass, fieldName); + } catch(ReflectiveOperationException e) { + throw new RuntimeException("Unexpected exception", e); + } + } + } + /* Now the ID mappings on synced entity data instances are probably all wrong. Fix that. */ + List dataEntries; + synchronized (allEntityDatas) { + if(fixNeeded) { + dataEntries = new ArrayList<>(allEntityDatas); + for(SynchedEntityData manager : dataEntries) { + Int2ObjectOpenHashMap> fixedMap = new Int2ObjectOpenHashMap<>(); + List> items = new ArrayList<>(manager.itemsById.values()); + for(SynchedEntityData.DataItem item : items) { + fixedMap.put(item.getAccessor().id, item); + } + manager.lock.writeLock().lock(); + try { + manager.itemsById.replaceAll((id, parameter) -> fixedMap.get((int)id)); + if(entriesArrayField != null) { + try { + SynchedEntityData.DataItem[] dataArray = new SynchedEntityData.DataItem[items.size()]; + for(int i = 0; i < dataArray.length; i++) { + dataArray[i] = fixedMap.get(i); + } + entriesArrayField.set(manager, dataArray); + } catch(ReflectiveOperationException e) { + ModernFix.LOGGER.error(e); + } + } + } finally { + manager.lock.writeLock().unlock(); + } + } + } + allEntityDatas.clear(); + } + }); + + context.get().setPacketHandled(true); + } } diff --git a/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java b/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java index e9bd7c9f..4c439b5d 100644 --- a/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java +++ b/src/main/java/org/embeddedt/modernfix/blockstate/BlockStateCacheHandler.java @@ -53,7 +53,7 @@ public class BlockStateCacheHandler { currentRebuildThread = null; } } else { - ModernFix.LOGGER.warn("Deferred blockstate cache rebuild"); + ModernFix.LOGGER.debug("Deferred blockstate cache rebuild"); } } diff --git a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index 93a70702..842f1b9c 100644 --- a/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -1,6 +1,7 @@ package org.embeddedt.modernfix.core.config; import net.minecraftforge.fml.loading.FMLLoader; +import net.minecraftforge.fml.loading.moddiscovery.ModInfo; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; @@ -22,6 +23,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("feature.measure_time", true); this.addMixinRule("feature.reduce_loading_screen_freezes", false); this.addMixinRule("perf.fast_registry_validation", true); + this.addMixinRule("perf.use_integrated_resources", true); this.addMixinRule("perf.remove_biome_temperature_cache", true); this.addMixinRule("perf.reduce_blockstate_cache_rebuilds", true); this.addMixinRule("perf.parallelize_model_loading", true); @@ -30,6 +32,7 @@ public class ModernFixEarlyConfig { this.addMixinRule("perf.cache_upgraded_structures", true); this.addMixinRule("bugfix.concurrency", true); this.addMixinRule("bugfix.edge_chunk_not_saved", true); + this.addMixinRule("perf.async_jei", true); this.addMixinRule("perf.thread_priorities", true); this.addMixinRule("perf.sync_executor_sleep", true); this.addMixinRule("perf.scan_cache", true); @@ -43,7 +46,8 @@ public class ModernFixEarlyConfig { /* off by default in 1.18 because it doesn't work as well */ this.addMixinRule("perf.faster_singleplayer_load", false); /* Keep this off if JEI isn't installed to prevent breaking vanilla gameplay */ - this.addMixinRule("perf.blast_search_trees", FMLLoader.getLoadingModList().getModFileById("jei") != null); + Optional jeiMod = FMLLoader.getLoadingModList().getMods().stream().filter(mod -> mod.getModId().equals("jei")).findFirst(); + this.addMixinRule("perf.blast_search_trees", jeiMod.isPresent() && jeiMod.get().getVersion().getMajorVersion() >= 10); this.addMixinRule("safety", true); this.addMixinRule("launch.transformer_cache", false); this.addMixinRule("launch.class_search_cache", true); @@ -52,6 +56,7 @@ public class ModernFixEarlyConfig { disableIfModPresent("mixin.perf.thread_priorities", "smoothboot"); disableIfModPresent("mixin.perf.async_jei", "modernui"); disableIfModPresent("mixin.perf.compress_biome_container", "chocolate"); + disableIfModPresent("mixin.bugfix.mc218112", "performant"); } private void disableIfModPresent(String configName, String... ids) { diff --git a/src/main/java/org/embeddedt/modernfix/duck/reuse_datapacks/ICachingResourceClient.java b/src/main/java/org/embeddedt/modernfix/duck/reuse_datapacks/ICachingResourceClient.java new file mode 100644 index 00000000..030967d9 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/duck/reuse_datapacks/ICachingResourceClient.java @@ -0,0 +1,10 @@ +package org.embeddedt.modernfix.duck.reuse_datapacks; + +import net.minecraft.server.ReloadableServerResources; + +import java.util.Collection; + +public interface ICachingResourceClient { + void setCachedResources(ReloadableServerResources r); + void setCachedDataPackConfig(Collection c); +} diff --git a/src/main/java/org/embeddedt/modernfix/entity/EntityDataIDSyncHandler.java b/src/main/java/org/embeddedt/modernfix/entity/EntityDataIDSyncHandler.java new file mode 100644 index 00000000..7be0a5ff --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/entity/EntityDataIDSyncHandler.java @@ -0,0 +1,69 @@ +package org.embeddedt.modernfix.entity; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; +import net.minecraftforge.event.OnDatapackSyncEvent; +import net.minecraftforge.eventbus.api.EventPriority; +import net.minecraftforge.eventbus.api.SubscribeEvent; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import net.minecraftforge.network.PacketDistributor; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.packet.EntityIDSyncPacket; +import org.embeddedt.modernfix.packet.PacketHandler; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class EntityDataIDSyncHandler { + private static Map, List>> fieldsToSyncMap; + @SubscribeEvent(priority = EventPriority.HIGHEST) + @SuppressWarnings("unchecked") + public static void onDatapackSyncEvent(OnDatapackSyncEvent event) { + if(event.getPlayer() != null) { + if(!ServerLifecycleHooks.getCurrentServer().isDedicatedServer() && event.getPlayerList().getPlayerCount() == 0) { + ModernFix.LOGGER.debug("Not syncing IDs on integrated server"); + return; + } + /* Compute the current set of serializer IDs in use and send them */ + try { + if(fieldsToSyncMap == null) { + fieldsToSyncMap = new HashMap<>(); + Field entityPoolField = ObfuscationReflectionHelper.findField(SynchedEntityData.class, "f_135343_"); + Map, Integer> entityPoolMap = (Map, Integer>)entityPoolField.get(null); + List fieldsToSync = new ArrayList<>(); + for(Class eClass : entityPoolMap.keySet()) { + fieldsToSync.clear(); + Field[] classFields = eClass.getDeclaredFields(); + for(Field field : classFields) { + if(!Modifier.isStatic(field.getModifiers())) + continue; + field.setAccessible(true); + Object o = field.get(null); + if(o != null && EntityDataAccessor.class.isAssignableFrom(o.getClass())) { + fieldsToSync.add(field); + } + } + for(Field field : fieldsToSync) { + int id = ((EntityDataAccessor)field.get(null)).id; + fieldsToSyncMap.computeIfAbsent(eClass, k -> new ArrayList<>()).add(Pair.of(field.getName(), id)); + } + } + } + EntityIDSyncPacket packet = new EntityIDSyncPacket(fieldsToSyncMap); + ModernFix.LOGGER.debug("Sending ID correction packet to client with " + fieldsToSyncMap.size() + " classes"); + PacketHandler.INSTANCE.send(PacketDistributor.PLAYER.with(event::getPlayer), packet); + } catch(ObfuscationReflectionHelper.UnableToFindFieldException | ReflectiveOperationException e) { + e.printStackTrace(); + } + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/core/SynchedEntityDataMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/core/SynchedEntityDataMixin.java new file mode 100644 index 00000000..67133e3f --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/core/SynchedEntityDataMixin.java @@ -0,0 +1,24 @@ +package org.embeddedt.modernfix.mixin.core; + +import net.minecraft.network.syncher.SynchedEntityData; +import net.minecraft.world.entity.Entity; +import org.embeddedt.modernfix.ModernFixClient; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; + +@Mixin(SynchedEntityData.class) +public class SynchedEntityDataMixin { + /** + * Store this in our set of all entity data objects. + * + * Not an ideal solution, but it should guarantee compatibility with mods. + */ + @Inject(method = "", at = @At("RETURN")) + private void storeInSet(Entity arg, CallbackInfo ci) { + synchronized (ModernFixClient.allEntityDatas) { + ModernFixClient.allEntityDatas.add((SynchedEntityData)(Object)this); + } + } +} diff --git a/src/main/java/org/embeddedt/modernfix/mixin/feature/branding/BrandingControlMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/feature/branding/BrandingControlMixin.java index eb2c9deb..129e5629 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/feature/branding/BrandingControlMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/feature/branding/BrandingControlMixin.java @@ -13,9 +13,9 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import java.util.List; import java.util.Optional; -@Mixin(value = BrandingControl.class, remap = false) +@Mixin(value = BrandingControl.class, remap = false, priority = 1100) public class BrandingControlMixin { - @Inject(method = "computeBranding", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModList;get()Lnet/minecraftforge/fml/ModList;"), locals = LocalCapture.CAPTURE_FAILHARD) + @Inject(method = "computeBranding", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModList;get()Lnet/minecraftforge/fml/ModList;"), locals = LocalCapture.CAPTURE_FAILHARD, require = 0) private static void addModernFixBranding(CallbackInfo ci, ImmutableList.Builder builder) { Optional mfContainer = ModList.get().getModContainerById("modernfix"); if(mfContainer.isPresent()) diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/blast_search_trees/MinecraftMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/blast_search_trees/MinecraftMixin.java index 64815e1d..b4d45f1d 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/blast_search_trees/MinecraftMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/blast_search_trees/MinecraftMixin.java @@ -2,7 +2,9 @@ package org.embeddedt.modernfix.mixin.perf.blast_search_trees; import net.minecraft.client.Minecraft; import net.minecraft.client.searchtree.SearchRegistry; +import net.minecraftforge.fml.ModContainer; import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.IModFileInfo; import org.embeddedt.modernfix.searchtree.DummySearchTree; import org.embeddedt.modernfix.searchtree.JEIBackedSearchTree; import org.spongepowered.asm.mixin.Final; @@ -12,6 +14,8 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import java.util.Optional; + @Mixin(Minecraft.class) public class MinecraftMixin { @Shadow @Final private SearchRegistry searchRegistry; @@ -19,7 +23,8 @@ public class MinecraftMixin { @Inject(method = "createSearchTrees", at = @At("HEAD"), cancellable = true) private void replaceSearchTrees(CallbackInfo ci) { ci.cancel(); - if(ModList.get().getModFileById("jei") != null) { + Optional jeiContainer = ModList.get().getModContainerById("jei"); + if(jeiContainer.isPresent() && jeiContainer.get().getModInfo().getVersion().getMajorVersion() >= 10) { this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, list -> new JEIBackedSearchTree(false)); this.searchRegistry.register(SearchRegistry.CREATIVE_TAGS, list -> new JEIBackedSearchTree(true)); } else { diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/thread_priorities/IntegratedServerMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/thread_priorities/IntegratedServerMixin.java index 59cf4753..ebc3a75e 100644 --- a/src/main/java/org/embeddedt/modernfix/mixin/perf/thread_priorities/IntegratedServerMixin.java +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/thread_priorities/IntegratedServerMixin.java @@ -19,7 +19,6 @@ public class IntegratedServerMixin { @Inject(method = "", at = @At("RETURN")) private void adjustServerPriority(Thread thread, Minecraft arg, LevelStorageSource.LevelStorageAccess arg2, PackRepository arg3, WorldStem arg4, Services arg5, ChunkProgressListenerFactory arg6, CallbackInfo ci) { int pri = ModernFixConfig.INTEGRATED_SERVER_PRIORITY.get(); - ModernFix.LOGGER.info("Changing server thread priority to " + pri); thread.setPriority(pri); } } diff --git a/src/main/java/org/embeddedt/modernfix/mixin/perf/use_integrated_resources/LootTableHelperMixin.java b/src/main/java/org/embeddedt/modernfix/mixin/perf/use_integrated_resources/LootTableHelperMixin.java new file mode 100644 index 00000000..8ec63f73 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/mixin/perf/use_integrated_resources/LootTableHelperMixin.java @@ -0,0 +1,20 @@ +package org.embeddedt.modernfix.mixin.perf.use_integrated_resources; + +import jeresources.util.LootTableHelper; +import net.minecraft.server.MinecraftServer; +import net.minecraft.world.level.Level; +import net.minecraftforge.server.ServerLifecycleHooks; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +@Mixin(LootTableHelper.class) +public class LootTableHelperMixin { + @Redirect(method = "getLootTables", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/level/Level;getServer()Lnet/minecraft/server/MinecraftServer;")) + private static MinecraftServer useIntegrated(Level level) { + MinecraftServer server = level.getServer(); + if(server != null) + return server; + return ServerLifecycleHooks.getCurrentServer(); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/packet/EntityIDSyncPacket.java b/src/main/java/org/embeddedt/modernfix/packet/EntityIDSyncPacket.java new file mode 100644 index 00000000..6fe2c6b1 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/packet/EntityIDSyncPacket.java @@ -0,0 +1,78 @@ +package org.embeddedt.modernfix.packet; + +import com.mojang.datafixers.util.Pair; +import net.minecraft.network.FriendlyByteBuf; +import net.minecraft.network.syncher.EntityDataAccessor; +import net.minecraft.world.entity.Entity; +import org.embeddedt.modernfix.ModernFix; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.*; + +public class EntityIDSyncPacket { + private Map, List>> map; + + public EntityIDSyncPacket(Map, List>> map) { + this.map = map; + } + + public Map, List>> getFieldInfo() { + return this.map; + } + + public EntityIDSyncPacket() { + this.map = new HashMap<>(); + } + + public void serialize(FriendlyByteBuf buf) { + buf.writeVarInt(map.keySet().size()); + for(Map.Entry, List>> entry : map.entrySet()) { + buf.writeUtf(entry.getKey().getName()); + buf.writeVarInt(entry.getValue().size()); + for(Pair field : entry.getValue()) { + buf.writeUtf(field.getFirst()); + buf.writeVarInt(field.getSecond()); + } + } + } + + @SuppressWarnings("unchecked") + public static EntityIDSyncPacket deserialize(FriendlyByteBuf buf) { + EntityIDSyncPacket self = new EntityIDSyncPacket(); + int numEntityClasses = buf.readVarInt(); + for(int i = 0; i < numEntityClasses; i++) { + String clzName = buf.readUtf(); + try { + Class clz; + try { + clz = Class.forName(clzName); + } catch(ClassNotFoundException e) { + ModernFix.LOGGER.warn("Entity class not found: {}", clzName); + break; + } + if(!Entity.class.isAssignableFrom(clz)) { + ModernFix.LOGGER.error("Not an entity: " + clzName); + break; + } + int numFields = buf.readVarInt(); + for(int j = 0; j < numFields; j++) { + String fieldName = buf.readUtf(); + int id = buf.readVarInt(); + Field f = clz.getDeclaredField(fieldName); + if(!Modifier.isStatic(f.getModifiers())) + continue; + f.setAccessible(true); + if(!EntityDataAccessor.class.isAssignableFrom(f.get(null).getClass())) { + ModernFix.LOGGER.error("Not a data accessor field: " + clz + "." + fieldName); + continue; + } + self.map.computeIfAbsent((Class)clz, k -> new ArrayList<>()).add(Pair.of(fieldName, id)); + } + } catch(ReflectiveOperationException e) { + ModernFix.LOGGER.error("Error deserializing packet", e); + } + } + return self; + } +} diff --git a/src/main/java/org/embeddedt/modernfix/packet/PacketHandler.java b/src/main/java/org/embeddedt/modernfix/packet/PacketHandler.java new file mode 100644 index 00000000..e489fea0 --- /dev/null +++ b/src/main/java/org/embeddedt/modernfix/packet/PacketHandler.java @@ -0,0 +1,22 @@ +package org.embeddedt.modernfix.packet; + +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.network.NetworkRegistry; +import net.minecraftforge.network.simple.SimpleChannel; +import org.embeddedt.modernfix.ModernFix; +import org.embeddedt.modernfix.ModernFixClient; + +public class PacketHandler { + private static final String PROTOCOL_VERSION = "1"; + public static final SimpleChannel INSTANCE = NetworkRegistry.newSimpleChannel( + new ResourceLocation(ModernFix.MODID, "main"), + () -> PROTOCOL_VERSION, + NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION), + NetworkRegistry.acceptMissingOr(PROTOCOL_VERSION) + ); + + public static void register() { + int id = 1; + INSTANCE.registerMessage(id++, EntityIDSyncPacket.class, EntityIDSyncPacket::serialize, EntityIDSyncPacket::deserialize, ModernFixClient::handleEntityIDSync); + } +} diff --git a/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java b/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java index cc386233..d2fbb230 100644 --- a/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java +++ b/src/main/java/org/embeddedt/modernfix/textures/StbStitcher.java @@ -11,12 +11,119 @@ import org.lwjgl.stb.STBRPNode; import org.lwjgl.stb.STBRPRect; import org.lwjgl.stb.STBRectPack; +import static java.lang.invoke.MethodHandles.*; +import static java.lang.invoke.MethodType.*; + +import java.lang.invoke.MethodHandle; +import java.sql.Ref; import java.util.ArrayList; import java.util.List; import java.util.stream.Stream; /* Source: https://github.com/GTNewHorizons/lwjgl3ify/blob/f21364cd3d178aef863458a2faa1f5718a4e350d/src/main/java/me/eigenraven/lwjgl3ify/textures/StbStitcher.java */ public class StbStitcher { + /* Most of this logic is to allow use of LWJGL versions where coordinates are short and versions where they are int */ + private static final MethodHandle MH_rect_shortSet, MH_rect_intSet, MH_rect_intX, MH_rect_intY, MH_rect_shortX, + MH_rect_shortY; + + static { + MethodHandle shortM = null, intM = null; + List exceptions = new ArrayList<>(); + try { + intM = publicLookup().findVirtual(STBRPRect.class, "set", methodType(STBRPRect.class, + int.class, /* id */ + int.class, + int.class, + int.class, + int.class, + boolean.class)); + } catch(ReflectiveOperationException e) { + exceptions.add(e); + } + try { + shortM = publicLookup().findVirtual(STBRPRect.class, "set", methodType(STBRPRect.class, + int.class, /* id */ + short.class, + short.class, + short.class, + short.class, + boolean.class)); + } catch(ReflectiveOperationException e) { + exceptions.add(e); + } + if(shortM == null && intM == null) { + IllegalStateException e = new IllegalStateException("An STBRPRect set method could not be located"); + exceptions.forEach(e::addSuppressed); + throw e; + } + MH_rect_shortSet = shortM; + MH_rect_intSet = intM; + /* Now look for X methods */ + exceptions.clear(); + try { + intM = publicLookup().findVirtual(STBRPRect.class, "x", methodType(int.class)); + } catch(ReflectiveOperationException e) { + exceptions.add(e); + } + try { + shortM = publicLookup().findVirtual(STBRPRect.class, "x", methodType(short.class)); + } catch(ReflectiveOperationException e) { + exceptions.add(e); + } + if(shortM == null && intM == null) { + IllegalStateException e = new IllegalStateException("An STBRPRect x() method could not be located"); + exceptions.forEach(e::addSuppressed); + throw e; + } + MH_rect_shortX = shortM; + MH_rect_intX = intM; + /* Assume that Y is the same */ + try { + if(MH_rect_shortX != null) { + MH_rect_shortY = publicLookup().findVirtual(STBRPRect.class, "y", methodType(short.class)); + MH_rect_intY = null; + } else { /* it must be int */ + MH_rect_intY = publicLookup().findVirtual(STBRPRect.class, "y", methodType(int.class)); + MH_rect_shortY = null; + } + } catch(ReflectiveOperationException e) { + throw new IllegalStateException("An STBRPRect y() method could not be located", e); + } + } + + private static STBRPRect setWrapper(STBRPRect rect, int id, int width, int height, int x, int y, boolean was_packed) { + try { + if(MH_rect_shortSet != null) + return (STBRPRect)MH_rect_shortSet.invokeExact(rect, id, (short)width, (short)height, (short)0, (short)0, false); + else + return (STBRPRect)MH_rect_intSet.invokeExact(rect, id, width, height, 0, 0, false); + } catch(Throwable e) { + throw new AssertionError(e); + } + } + + private static int getX(STBRPRect rect) { + try { + if(MH_rect_shortX != null) + return (short)MH_rect_shortX.invokeExact(rect); + else + return (int)MH_rect_intX.invokeExact(rect); + } catch(Throwable e) { + throw new AssertionError(e); + } + } + + private static int getY(STBRPRect rect) { + try { + if(MH_rect_shortX != null) + return (short)MH_rect_shortY.invokeExact(rect); + else + return (int)MH_rect_intY.invokeExact(rect); + } catch(Throwable e) { + throw new AssertionError(e); + } + } + public static Pair, List> packRects(Stitcher.Holder[] holders) { int holderSize = holders.length; @@ -36,7 +143,9 @@ public class StbStitcher { int height = holder.height; // The ID here is just the array index, for easy lookup later - rectBuf.get(j).set(j, (short)width, (short)height, (short)0, (short)0, false); + STBRPRect rect = rectBuf.get(j); + + setWrapper(rect, j, width, height, 0, 0, false); sqSize += (width * height); } @@ -63,7 +172,7 @@ public class StbStitcher { } // Initialize the sprite now with the position and size that we've calculated so far - infoList.add(new LoadableSpriteInfo(holder.spriteInfo, width, height, rect.x(), rect.y())); + infoList.add(new LoadableSpriteInfo(holder.spriteInfo, width, height, getX(rect), getY(rect))); //holder.spriteInfo.initSprite(size, size, rect.x(), rect.y(), false); } diff --git a/src/main/java/org/embeddedt/modernfix/util/FileUtil.java b/src/main/java/org/embeddedt/modernfix/util/FileUtil.java index 7e8dda59..51045bda 100644 --- a/src/main/java/org/embeddedt/modernfix/util/FileUtil.java +++ b/src/main/java/org/embeddedt/modernfix/util/FileUtil.java @@ -1,10 +1,22 @@ package org.embeddedt.modernfix.util; import java.io.File; +import java.util.regex.Pattern; public class FileUtil { public static File childFile(File file) { file.getParentFile().mkdirs(); return file; } + + private static final Pattern SLASH_PATTERN = Pattern.compile("(?:\\\\+|\\/+)"); + + /** + * Normalize a path by removing double slashes, etc. + * @param path input path + * @return a normalized version of the path + */ + public static String normalize(String path) { + return SLASH_PATTERN.matcher(path).replaceAll("/"); + } } diff --git a/src/main/resources/META-INF/accesstransformer.cfg b/src/main/resources/META-INF/accesstransformer.cfg index 84ad721f..400ca921 100644 --- a/src/main/resources/META-INF/accesstransformer.cfg +++ b/src/main/resources/META-INF/accesstransformer.cfg @@ -8,4 +8,7 @@ public net.minecraft.client.renderer.model.ModelBakery field_217849_F # unbakedC public net.minecraft.client.renderer.texture.Stitcher$Holder public net.minecraft.util.thread.BlockableEventLoop m_18699_()V # runAllTasks public net.minecraft.server.MinecraftServer f_129726_ # nextTickTime -public net.minecraft.client.Minecraft f_90999_ # progressListener \ No newline at end of file +public net.minecraft.client.Minecraft f_90999_ # progressListener +public-f net.minecraft.network.syncher.EntityDataAccessor f_135010_ # id +public-f net.minecraft.network.syncher.SynchedEntityData f_135345_ # itemsById +public-f net.minecraft.network.syncher.SynchedEntityData f_135346_ # lock diff --git a/src/main/resources/META-INF/mods.toml b/src/main/resources/META-INF/mods.toml index 78d669d2..f895414c 100644 --- a/src/main/resources/META-INF/mods.toml +++ b/src/main/resources/META-INF/mods.toml @@ -60,6 +60,6 @@ side = "BOTH" modId = "jei" mandatory = false # This version range declares a minimum of the current minecraft version up to but not including the next major version -versionRange = "[9.9999,)" +versionRange = "[11,)" ordering = "BEFORE" side = "CLIENT" diff --git a/src/main/resources/assets/modernfix/lang/en_us.json b/src/main/resources/assets/modernfix/lang/en_us.json index 8b1b7ad4..b757242b 100644 --- a/src/main/resources/assets/modernfix/lang/en_us.json +++ b/src/main/resources/assets/modernfix/lang/en_us.json @@ -1,5 +1,6 @@ { "modernfix.jei_load": "Loading JEI, this may take a while", + "modernfix.no_lazydfu": "ModernFix detected that DFU rules were compiled on startup. This slows down game launching. Installing LazyDFU to resolve this is highly recommended.", "asynclocator.map.locating": "Map (Locating...)", "asynclocator.map.none": "Map (No nearby feature found)" } diff --git a/src/main/resources/assets/modernfix/lang/zh_cn.json b/src/main/resources/assets/modernfix/lang/zh_cn.json index 0c9c92a5..f8e78167 100644 --- a/src/main/resources/assets/modernfix/lang/zh_cn.json +++ b/src/main/resources/assets/modernfix/lang/zh_cn.json @@ -1,5 +1,6 @@ { - "modernfix.jei_load": "正在加载JEI,这需要一点时间", - "asynclocator.map.locating": "地图(正在定位...)", + "modernfix.jei_load": "正在加载JEI,这需要一点时间。", + "modernfix.no_lazydfu": "现代化修复检测到DFU规则在游戏启动时已被编译。这会降低游戏启动速度。非常推荐安装DFU载入优化来解决这个问题。", + "asynclocator.map.locating": "地图(定位中……)", "asynclocator.map.none": "地图(未能找到相关地物)" } diff --git a/src/main/resources/modernfix.mixins.json b/src/main/resources/modernfix.mixins.json index 772b9c46..c3b8cc03 100644 --- a/src/main/resources/modernfix.mixins.json +++ b/src/main/resources/modernfix.mixins.json @@ -32,6 +32,7 @@ ], "client": [ "core.MinecraftMixin", + "core.SynchedEntityDataMixin", "feature.measure_time.MinecraftMixin", "feature.reduce_loading_screen_freezes.ModelBakeryMixin", "bugfix.concurrency.MinecraftMixin", @@ -56,6 +57,7 @@ "perf.cache_model_materials.VanillaModelMixin", "perf.cache_model_materials.MultipartMixin", "perf.faster_texture_stitching.StitcherMixin", + "perf.use_integrated_resources.LootTableHelperMixin", "perf.faster_singleplayer_load.MinecraftServerMixin" ], "injectors": {