Merge branch '1.20' into dev/1.20.2

This commit is contained in:
embeddedt 2023-08-09 11:35:59 -04:00
commit 84c72f8da8
No known key found for this signature in database
GPG Key ID: A69433EC199B5613
22 changed files with 305 additions and 136 deletions

View File

@ -20,7 +20,7 @@ public class MinecraftMixin {
@Inject(method = "tick", at = @At("HEAD")) @Inject(method = "tick", at = @At("HEAD"))
private void onClientTick(CallbackInfo ci) { private void onClientTick(CallbackInfo ci) {
if(this.overlay == null) { if(this.overlay == null && ModernFixClient.INSTANCE != null) {
ModernFixClient.INSTANCE.onGameLaunchFinish(); ModernFixClient.INSTANCE.onGameLaunchFinish();
} }
} }

View File

@ -33,7 +33,6 @@ public abstract class MinecraftMixin {
if(provider == null) if(provider == null)
return; return;
ModernFix.LOGGER.info("Replacing search trees with '{}' provider", provider.getName()); ModernFix.LOGGER.info("Replacing search trees with '{}' provider", provider.getName());
mfix$runItemFillingQuirk();
SearchRegistry.TreeBuilderSupplier<ItemStack> nameSupplier = list -> provider.getSearchTree(false); SearchRegistry.TreeBuilderSupplier<ItemStack> nameSupplier = list -> provider.getSearchTree(false);
SearchRegistry.TreeBuilderSupplier<ItemStack> tagSupplier = list -> provider.getSearchTree(true); SearchRegistry.TreeBuilderSupplier<ItemStack> tagSupplier = list -> provider.getSearchTree(true);
this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, nameSupplier); this.searchRegistry.register(SearchRegistry.CREATIVE_NAMES, nameSupplier);
@ -50,8 +49,4 @@ public abstract class MinecraftMixin {
GLFW.glfwSetErrorCallback(oldCb); GLFW.glfwSetErrorCallback(oldCb);
ci.cancel(); ci.cancel();
} }
private void mfix$runItemFillingQuirk() {
// currently unimplemented on 1.19.4+
}
} }

View File

@ -0,0 +1,19 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.types.Type;
import net.minecraft.world.level.block.entity.BlockEntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Prevent fetchChoiceType calls from loading DFU early. Vanilla doesn't need the return values here.
*/
@Mixin(BlockEntityType.class)
public class BlockEntityTypeMixin {
@Redirect(method = "register", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;fetchChoiceType(Lcom/mojang/datafixers/DSL$TypeReference;Ljava/lang/String;)Lcom/mojang/datafixers/types/Type;"))
private static Type<?> skipSchemaCheck(DSL.TypeReference ref, String s) {
return null;
}
}

View File

@ -0,0 +1,19 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
import com.mojang.datafixers.DSL;
import com.mojang.datafixers.types.Type;
import net.minecraft.world.entity.EntityType;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Redirect;
/**
* Prevent fetchChoiceType calls from loading DFU early. Vanilla doesn't need the return values here.
*/
@Mixin(EntityType.Builder.class)
public class EntityTypeBuilderMixin {
@Redirect(method = "build", at = @At(value = "INVOKE", target = "Lnet/minecraft/Util;fetchChoiceType(Lcom/mojang/datafixers/DSL$TypeReference;Ljava/lang/String;)Lcom/mojang/datafixers/types/Type;"))
private Type<?> skipSchemaCheck(DSL.TypeReference ref, String s) {
return null;
}
}

View File

@ -1,15 +0,0 @@
package org.embeddedt.modernfix.common.mixin.perf.dynamic_dfu;
import net.minecraft.SharedConstants;
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(SharedConstants.class)
public class SharedConstantsMixin {
@Inject(method = "<clinit>", at = @At("RETURN"))
private static void skipSchemaCheck(CallbackInfo ci) {
SharedConstants.CHECK_DATA_FIXER_SCHEMA = false;
}
}

View File

@ -1,7 +1,14 @@
package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds; package org.embeddedt.modernfix.common.mixin.perf.reduce_blockstate_cache_rebuilds;
import com.google.common.collect.ImmutableMap;
import com.mojang.serialization.MapCodec;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockBehaviour; import net.minecraft.world.level.block.state.BlockBehaviour;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.state.StateHolder;
import net.minecraft.world.level.block.state.properties.Property;
import net.minecraft.world.level.material.FluidState; import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.material.Fluids;
import org.embeddedt.modernfix.duck.IBlockState; import org.embeddedt.modernfix.duck.IBlockState;
import org.objectweb.asm.Opcodes; import org.objectweb.asm.Opcodes;
import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.Dynamic;
@ -14,13 +21,21 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
@Mixin(BlockBehaviour.BlockStateBase.class) @Mixin(BlockBehaviour.BlockStateBase.class)
public abstract class BlockStateBaseMixin implements IBlockState { public abstract class BlockStateBaseMixin extends StateHolder<Block, BlockState> implements IBlockState {
protected BlockStateBaseMixin(Block object, ImmutableMap<Property<?>, Comparable<?>> immutableMap, MapCodec<BlockState> mapCodec) {
super(object, immutableMap, mapCodec);
}
private static final FluidState MFIX$VANILLA_DEFAULT_FLUID = Fluids.EMPTY.defaultFluidState();
@Shadow public abstract void initCache(); @Shadow public abstract void initCache();
@Shadow private BlockBehaviour.BlockStateBase.Cache cache; @Shadow private BlockBehaviour.BlockStateBase.Cache cache;
@Shadow private FluidState fluidState; @Shadow private FluidState fluidState;
@Shadow private boolean isRandomlyTicking; @Shadow private boolean isRandomlyTicking;
@Shadow protected abstract BlockState asState();
private volatile boolean cacheInvalid = false; private volatile boolean cacheInvalid = false;
private static boolean buildingCache = false; private static boolean buildingCache = false;
@Override @Override
@ -72,7 +87,11 @@ public abstract class BlockStateBaseMixin implements IBlockState {
ordinal = 0 ordinal = 0
), require = 0) ), require = 0)
private FluidState genCacheBeforeGettingFluid(BlockBehaviour.BlockStateBase base) { private FluidState genCacheBeforeGettingFluid(BlockBehaviour.BlockStateBase base) {
generateCache(base); // don't generate the full cache here as mods will iterate for the fluid state a lot
// assume blockstates will not change their contained fluidstate at runtime more than once
// this is how Lithium's implementation used to work, so it should be fine
if(this.cacheInvalid && this.fluidState == MFIX$VANILLA_DEFAULT_FLUID)
this.fluidState = this.owner.getFluidState(this.asState());
return this.fluidState; return this.fluidState;
} }
@ -83,7 +102,8 @@ public abstract class BlockStateBaseMixin implements IBlockState {
ordinal = 0 ordinal = 0
)) ))
private boolean genCacheBeforeGettingTicking(BlockBehaviour.BlockStateBase base) { private boolean genCacheBeforeGettingTicking(BlockBehaviour.BlockStateBase base) {
generateCache(base); if(this.cacheInvalid)
return this.owner.isRandomlyTicking(this.asState());
return this.isRandomlyTicking; return this.isRandomlyTicking;
} }

View File

@ -173,6 +173,7 @@ public class ModernFixEarlyConfig {
.put("mixin.devenv", isDevEnv) .put("mixin.devenv", isDevEnv)
.put("mixin.perf.remove_spawn_chunks", isDevEnv) .put("mixin.perf.remove_spawn_chunks", isDevEnv)
.putConditionally(() -> !isFabric, "mixin.bugfix.fix_config_crashes", true) .putConditionally(() -> !isFabric, "mixin.bugfix.fix_config_crashes", true)
.putConditionally(() -> !isFabric, "mixin.bugfix.forge_at_inject_error", true)
.putConditionally(() -> isFabric, "mixin.perf.clear_fabric_mapping_tables", false) .putConditionally(() -> isFabric, "mixin.perf.clear_fabric_mapping_tables", false)
.build(); .build();

View File

@ -9,9 +9,12 @@ import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.BlockModelRotation; import net.minecraft.client.resources.model.BlockModelRotation;
import net.minecraft.client.resources.model.ModelBakery; import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.client.resources.model.ModelResourceLocation;
import net.minecraft.core.Direction; import net.minecraft.core.Direction;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.util.RandomSource; import net.minecraft.util.RandomSource;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.apache.commons.lang3.tuple.Triple; import org.apache.commons.lang3.tuple.Triple;
import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.duck.IExtendedModelBakery;
@ -106,6 +109,27 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
public boolean containsValue(Object o) { public boolean containsValue(Object o) {
return permanentOverrides.containsValue(o) || bakedCache.containsValue(o); return permanentOverrides.containsValue(o) || bakedCache.containsValue(o);
} }
private static boolean isVanillaTopLevelModel(ResourceLocation location) {
if(location instanceof ModelResourceLocation) {
try {
ModelResourceLocation mrl = (ModelResourceLocation)location;
ResourceLocation registryKey = new ResourceLocation(mrl.getNamespace(), mrl.getPath());
// check for standard inventory model
if(mrl.getVariant().equals("inventory") && BuiltInRegistries.ITEM.containsKey(registryKey))
return true;
Optional<Block> blockOpt = BuiltInRegistries.BLOCK.getOptional(registryKey);
if(blockOpt.isPresent()) {
return ModelBakeryHelpers.getBlockStatesForMRL(blockOpt.get().getStateDefinition(), mrl).size() > 0;
}
} catch(RuntimeException ignored) {
// can occur if the MRL is not valid for that blockstate, ignore
}
}
if(location.getNamespace().equals("minecraft") && location.getPath().equals("builtin/missing"))
return true;
return false;
}
@Override @Override
public BakedModel get(Object o) { public BakedModel get(Object o) {
@ -120,11 +144,11 @@ public class DynamicBakedModelProvider implements Map<ResourceLocation, BakedMod
model = missingModel; model = missingModel;
} }
if(model == missingModel) { if(model == missingModel) {
// to correctly emulate the original map, we return null for missing models // to correctly emulate the original map, we return null for missing models, unless they are top-level
permanentOverrides.put((ResourceLocation) o, null); model = isVanillaTopLevelModel((ResourceLocation)o) ? model : null;
return null; permanentOverrides.put((ResourceLocation) o, model);
} else }
return model; return model;
} }
} }

View File

@ -7,6 +7,7 @@
"modernfix.perf_mod_warning": "It is recommended to install the mods, but the warning(s) can be disabled in the ModernFix config.", "modernfix.perf_mod_warning": "It is recommended to install the mods, but the warning(s) can be disabled in the ModernFix config.",
"modernfix.config": "ModernFix mixin config", "modernfix.config": "ModernFix mixin config",
"modernfix.config.done_restart": "Done (restart required)", "modernfix.config.done_restart": "Done (restart required)",
"modernfix.message.reload_config": "Run /mfrc after changing configs on disk for them to take effect.",
"modernfix.option.on": "on", "modernfix.option.on": "on",
"modernfix.option.off": "off", "modernfix.option.off": "off",
"modernfix.option.disabled": "disabled", "modernfix.option.disabled": "disabled",

View File

@ -174,6 +174,14 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
return ImmutableList.of(); return ImmutableList.of();
} }
/**
* Make a copy of the top-level model list to avoid CME if more models get loaded here.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;", ordinal = 0))
private Collection<?> copyTopLevelModelList(Map<?, ?> map) {
return new ArrayList<>(map.values());
}
private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter; private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter;
@Inject(method = "bakeModels", at = @At("HEAD")) @Inject(method = "bakeModels", at = @At("HEAD"))

View File

@ -0,0 +1,36 @@
package org.embeddedt.modernfix.forge.classloading;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.loading.moddiscovery.ModFile;
import net.minecraftforge.fml.loading.moddiscovery.ModValidator;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.apache.commons.lang3.tuple.Pair;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.util.CommonModUtil;
import java.nio.file.Path;
import java.util.List;
import java.util.stream.Collectors;
public class ATInjector {
public static void injectModATs() {
CommonModUtil.runWithoutCrash(() -> {
ModValidator validator = ObfuscationReflectionHelper.getPrivateValue(FMLLoader.class, null, "modValidator");
List<ModFile> modFiles = ObfuscationReflectionHelper.getPrivateValue(ModValidator.class, validator, "candidateMods");
List<Pair<ModFile, Path>> list = modFiles.stream()
.filter(file -> file.getAccessTransformer().isPresent())
.map(file -> Pair.of(file, file.getAccessTransformer().get()))
.collect(Collectors.toList());
if(list.size() > 0) {
ModernFixMixinPlugin.instance.logger.warn("Applying ATs from {} mods despite being in errored state, this might cause a crash!", list.size());
for(var pair : list) {
try {
FMLLoader.addAccessTransformer(pair.getRight(), pair.getLeft());
} catch(RuntimeException e) {
ModernFixMixinPlugin.instance.logger.error("Exception occured applying AT from {}", pair.getLeft().getFileName(), e);
}
}
}
}, "applying mod ATs in errored state");
}
}

View File

@ -8,6 +8,9 @@ import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import java.util.Optional; import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer; import java.util.function.Consumer;
public class ConfigFixer { public class ConfigFixer {
@ -35,6 +38,7 @@ public class ConfigFixer {
private static class LockingConfigHandler implements Consumer<IConfigEvent> { private static class LockingConfigHandler implements Consumer<IConfigEvent> {
private final Consumer<IConfigEvent> actualHandler; private final Consumer<IConfigEvent> actualHandler;
private final String modId; private final String modId;
private final Lock lock = new ReentrantLock();
LockingConfigHandler(String id, Consumer<IConfigEvent> actualHandler) { LockingConfigHandler(String id, Consumer<IConfigEvent> actualHandler) {
this.modId = id; this.modId = id;
@ -43,14 +47,17 @@ public class ConfigFixer {
@Override @Override
public void accept(IConfigEvent modConfigEvent) { public void accept(IConfigEvent modConfigEvent) {
Object cfgObj = NightConfigFixer.toWriteSyncConfig(modConfigEvent.getConfig().getConfigData()); try {
if(cfgObj != null) { if(lock.tryLock(2, TimeUnit.SECONDS)) {
// don't synchronize on 'this' as it produces a deadlock when used alongside NightConfigFixer try {
synchronized (cfgObj) { this.actualHandler.accept(modConfigEvent);
this.actualHandler.accept(modConfigEvent); } finally {
} lock.unlock();
} else { }
this.actualHandler.accept(modConfigEvent); } else
ModernFix.LOGGER.error("Failed to post config event for {}, someone else is holding the lock", modId);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
} }
} }

View File

@ -1,29 +1,29 @@
package org.embeddedt.modernfix.forge.config; package org.embeddedt.modernfix.forge.config;
import com.electronwill.nightconfig.core.file.CommentedFileConfig;
import com.electronwill.nightconfig.core.file.FileConfig;
import com.electronwill.nightconfig.core.file.FileWatcher; import com.electronwill.nightconfig.core.file.FileWatcher;
import cpw.mods.modlauncher.api.LamdbaExceptionUtils; import cpw.mods.modlauncher.api.LamdbaExceptionUtils;
import it.unimi.dsi.fastutil.objects.ReferenceOpenHashSet; import net.minecraft.client.Minecraft;
import net.minecraft.network.chat.Component;
import net.minecraftforge.fml.loading.FMLLoader;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.util.CommonModUtil; import org.embeddedt.modernfix.util.CommonModUtil;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.nio.file.Path; import java.nio.file.Path;
import java.util.Collections; import java.util.ArrayList;
import java.util.HashMap; import java.util.LinkedHashSet;
import java.util.Map; import java.util.List;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Function; import java.util.function.Function;
/**
* Relatively simple patch to wait for config saving to finish, made complex by Night Config classes being package-private,
* and Forge not allowing mixins into libraries.
*/
public class NightConfigFixer { public class NightConfigFixer {
public static final LinkedHashSet<Runnable> configsToReload = new LinkedHashSet<>();
public static void monitorFileWatcher() { public static void monitorFileWatcher() {
if(!ModernFixMixinPlugin.instance.isOptionEnabled("bugfix.fix_config_crashes.NightConfigFixerMixin"))
return;
CommonModUtil.runWithoutCrash(() -> { CommonModUtil.runWithoutCrash(() -> {
FileWatcher watcher = FileWatcher.defaultInstance(); FileWatcher watcher = FileWatcher.defaultInstance();
Field field = FileWatcher.class.getDeclaredField("watchedFiles"); Field field = FileWatcher.class.getDeclaredField("watchedFiles");
@ -35,34 +35,26 @@ public class NightConfigFixer {
}, "replacing Night Config watchedFiles map"); }, "replacing Night Config watchedFiles map");
} }
private static final Class<?> WATCHED_FILE = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.FileWatcher$WatchedFile")); public static void runReloads() {
private static final Field CHANGE_HANDLER = ObfuscationReflectionHelper.findField(WATCHED_FILE, "changeHandler"); List<Runnable> runnablesToRun;
synchronized (configsToReload) {
public static final Class<?> WRITE_SYNC_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.WriteSyncFileConfig")); runnablesToRun = new ArrayList<>(configsToReload);
private static final Class<?> AUTOSAVE_CONFIG = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.AutosaveCommentedFileConfig")); configsToReload.clear();
private static final Field AUTOSAVE_FILECONFIG = ObfuscationReflectionHelper.findField(AUTOSAVE_CONFIG, "fileConfig"); }
for(Runnable r : runnablesToRun) {
private static final Field CURRENTLY_WRITING = ObfuscationReflectionHelper.findField(WRITE_SYNC_CONFIG, "currentlyWriting"); try {
r.run();
private static final Map<Class<?>, Field> CONFIG_WATCHER_TO_CONFIG_FIELD = Collections.synchronizedMap(new HashMap<>()); } catch(RuntimeException e) {
e.printStackTrace();
private static Field getCurrentlyWritingFieldOnWatcher(Object watcher) {
return CONFIG_WATCHER_TO_CONFIG_FIELD.computeIfAbsent(watcher.getClass(), clz -> {
while(clz != null && clz != Object.class) {
for(Field f : clz.getDeclaredFields()) {
if(CommentedFileConfig.class.isAssignableFrom(f.getType())) {
f.setAccessible(true);
ModernFixMixinPlugin.instance.logger.debug("Found CommentedFileConfig: field '{}' on {}", f.getName(), clz.getName());
return f;
}
}
clz = clz.getSuperclass();
} }
return null; }
}); ModernFix.LOGGER.info("Processed {} config reloads", runnablesToRun.size());
} }
static class MonitoringMap extends ConcurrentHashMap<Path, Object> { static class MonitoringMap extends ConcurrentHashMap<Path, Object> {
private static final Class<?> WATCHED_FILE = LamdbaExceptionUtils.uncheck(() -> Class.forName("com.electronwill.nightconfig.core.file.FileWatcher$WatchedFile"));
private static final Field CHANGE_HANDLER = ObfuscationReflectionHelper.findField(WATCHED_FILE, "changeHandler");
public MonitoringMap(ConcurrentHashMap<Path, ?> oldMap) { public MonitoringMap(ConcurrentHashMap<Path, ?> oldMap) {
super(oldMap); super(oldMap);
} }
@ -82,24 +74,16 @@ public class NightConfigFixer {
} }
} }
private static final Set<Class<?>> UNKNOWN_FILE_CONFIG_CLASSES = Collections.synchronizedSet(new ReferenceOpenHashSet<>()); private static long lastConfigTrigger = System.nanoTime();
public static Object toWriteSyncConfig(Object config) { private static void triggerConfigMessage() {
if(config == null) if((System.nanoTime() - lastConfigTrigger) >= TimeUnit.SECONDS.toNanos(5)) {
return null; lastConfigTrigger = System.nanoTime();
try { Minecraft.getInstance().execute(() -> {
if(WRITE_SYNC_CONFIG.isAssignableFrom(config.getClass())) { if(Minecraft.getInstance().level != null) {
return config; Minecraft.getInstance().gui.getChat().addMessage(Component.translatable("modernfix.message.reload_config"));
} else if(AUTOSAVE_CONFIG.isAssignableFrom(config.getClass())) { }
FileConfig fc = (FileConfig)AUTOSAVE_FILECONFIG.get(config); });
return toWriteSyncConfig(fc);
} else {
if (UNKNOWN_FILE_CONFIG_CLASSES.add(config.getClass()))
ModernFixMixinPlugin.instance.logger.warn("Unexpected FileConfig class: {}", config.getClass().getName());
return null;
}
} catch(ReflectiveOperationException e) {
return null;
} }
} }
@ -110,47 +94,17 @@ public class NightConfigFixer {
this.configTracker = r; this.configTracker = r;
} }
private void protectFromSaving(FileConfig config, Runnable runnable) throws ReflectiveOperationException {
Object writeSyncConfig = toWriteSyncConfig(config);
if(writeSyncConfig != null) {
// keep trying to write, releasing the config lock each time in case something else needs to lock it
// for any reason
while(true) {
// acquiring synchronized block here should in theory prevent any other concurrent loads/saves, based
// off WriteSyncFileConfig implementation
synchronized (writeSyncConfig) {
if(CURRENTLY_WRITING.getBoolean(writeSyncConfig)) {
ModernFixMixinPlugin.instance.logger.fatal("Config being written during load!!!");
try { Thread.sleep(500); } catch(InterruptedException e) { Thread.currentThread().interrupt(); }
continue;
}
// at this point, currentlyWriting is false, and we acquired synchronized lock, should be good to
// go
runnable.run();
break;
}
}
} else {
runnable.run();
}
}
/** /**
* This entrypoint runs when the file watcher has detected a change on the config file. Before passing * Add the config runnable to the list to be processed by the main thread.
* this through to Forge, use reflection hacks to confirm the config system is not still writing to the file.
* If it is, spin until writing finishes. Immediately returning might result in the event never being observed.
*/ */
@Override @Override
public void run() { public void run() {
try { synchronized(configsToReload) {
Field theField = getCurrentlyWritingFieldOnWatcher(this.configTracker); if(FMLLoader.getDist().isClient())
if(theField != null) { triggerConfigMessage();
CommentedFileConfig cfg = (CommentedFileConfig)theField.get(this.configTracker); if(configsToReload.size() == 0)
// will synchronize and check saving flag ModernFixMixinPlugin.instance.logger.info("Please use /{} to reload any changed mod config files", FMLLoader.getDist().isDedicatedServer() ? "mfsrc" : "mfrc");
protectFromSaving(cfg, configTracker); configsToReload.add(configTracker);
}
} catch(ReflectiveOperationException e) {
e.printStackTrace();
} }
} }
} }

View File

@ -0,0 +1,5 @@
package org.embeddedt.modernfix.forge.dynresources;
public interface IModelBakerImpl {
void mfix$ignoreCache();
}

View File

@ -6,6 +6,7 @@ import com.google.common.collect.Sets;
import com.google.common.graph.GraphBuilder; import com.google.common.graph.GraphBuilder;
import com.google.common.graph.MutableGraph; import com.google.common.graph.MutableGraph;
import net.minecraft.client.resources.model.BakedModel; import net.minecraft.client.resources.model.BakedModel;
import net.minecraft.client.resources.model.ModelBakery;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraft.world.level.block.Block; import net.minecraft.world.level.block.Block;
@ -14,6 +15,7 @@ import net.minecraftforge.fml.ModContainer;
import net.minecraftforge.fml.ModList; import net.minecraftforge.fml.ModList;
import net.minecraftforge.forgespi.language.IModInfo; import net.minecraftforge.forgespi.language.IModInfo;
import net.minecraftforge.registries.ForgeRegistries; import net.minecraftforge.registries.ForgeRegistries;
import org.embeddedt.modernfix.ModernFix;
import org.embeddedt.modernfix.dynamicresources.ModelLocationCache; import org.embeddedt.modernfix.dynamicresources.ModelLocationCache;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
@ -69,12 +71,23 @@ public class ModelBakeEventHelper {
if(modIdsToInclude.stream().noneMatch(INCOMPATIBLE_MODS::contains)) if(modIdsToInclude.stream().noneMatch(INCOMPATIBLE_MODS::contains))
return this.modelRegistry; return this.modelRegistry;
Set<ResourceLocation> ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace())); Set<ResourceLocation> ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace()));
BakedModel missingModel = modelRegistry.get(ModelBakery.MISSING_MODEL_LOCATION);
return new ForwardingMap<ResourceLocation, BakedModel>() { return new ForwardingMap<ResourceLocation, BakedModel>() {
@Override @Override
protected Map<ResourceLocation, BakedModel> delegate() { protected Map<ResourceLocation, BakedModel> delegate() {
return modelRegistry; return modelRegistry;
} }
@Override
public BakedModel get(@Nullable Object key) {
BakedModel model = super.get(key);
if(model == null && key != null && modIdsToInclude.contains(((ResourceLocation)key).getNamespace())) {
ModernFix.LOGGER.warn("Model {} is missing, but was requested in model bake event. Returning missing model", key);
return missingModel;
}
return model;
}
@Override @Override
public Set<ResourceLocation> keySet() { public Set<ResourceLocation> keySet() {
return ourModelLocations; return ourModelLocations;

View File

@ -1,12 +1,15 @@
package org.embeddedt.modernfix.forge.init; package org.embeddedt.modernfix.forge.init;
import com.mojang.blaze3d.platform.InputConstants; import com.mojang.blaze3d.platform.InputConstants;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.client.KeyMapping; import net.minecraft.client.KeyMapping;
import net.minecraft.client.Minecraft; import net.minecraft.client.Minecraft;
import net.minecraft.client.gui.components.DebugScreenOverlay; import net.minecraft.client.gui.components.DebugScreenOverlay;
import net.minecraft.commands.CommandSourceStack;
import net.minecraftforge.client.ConfigScreenHandler; import net.minecraftforge.client.ConfigScreenHandler;
import net.minecraftforge.client.event.CustomizeGuiOverlayEvent; import net.minecraftforge.client.event.CustomizeGuiOverlayEvent;
import net.minecraftforge.client.event.RecipesUpdatedEvent; import net.minecraftforge.client.event.RecipesUpdatedEvent;
import net.minecraftforge.client.event.RegisterClientCommandsEvent;
import net.minecraftforge.client.event.RegisterKeyMappingsEvent; import net.minecraftforge.client.event.RegisterKeyMappingsEvent;
import net.minecraftforge.client.gui.overlay.ForgeGui; import net.minecraftforge.client.gui.overlay.ForgeGui;
import net.minecraftforge.client.settings.KeyConflictContext; import net.minecraftforge.client.settings.KeyConflictContext;
@ -20,6 +23,7 @@ import net.minecraftforge.fml.ModLoadingContext;
import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext; import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
import net.minecraftforge.fml.util.ObfuscationReflectionHelper; import net.minecraftforge.fml.util.ObfuscationReflectionHelper;
import org.embeddedt.modernfix.ModernFixClient; import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.forge.config.NightConfigFixer;
import org.embeddedt.modernfix.screen.ModernFixConfigScreen; import org.embeddedt.modernfix.screen.ModernFixConfigScreen;
import java.util.ArrayList; import java.util.ArrayList;
@ -51,6 +55,15 @@ public class ModernFixClientForge {
} }
} }
@SubscribeEvent(priority = EventPriority.LOW)
public void onClientChat(RegisterClientCommandsEvent event) {
event.getDispatcher().register(LiteralArgumentBuilder.<CommandSourceStack>literal("mfrc")
.executes(context -> {
NightConfigFixer.runReloads();
return 1;
}));
}
private static final List<String> brandingList = new ArrayList<>(); private static final List<String> brandingList = new ArrayList<>();
@SubscribeEvent(priority = EventPriority.HIGHEST) @SubscribeEvent(priority = EventPriority.HIGHEST)

View File

@ -1,11 +1,14 @@
package org.embeddedt.modernfix.forge.init; package org.embeddedt.modernfix.forge.init;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import net.minecraft.commands.CommandSourceStack;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import net.minecraft.world.item.Item; import net.minecraft.world.item.Item;
import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.Dist;
import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.common.MinecraftForge;
import net.minecraftforge.event.OnDatapackSyncEvent; import net.minecraftforge.event.OnDatapackSyncEvent;
import net.minecraftforge.event.RegisterCommandsEvent;
import net.minecraftforge.event.server.ServerStartedEvent; import net.minecraftforge.event.server.ServerStartedEvent;
import net.minecraftforge.event.server.ServerStoppedEvent; import net.minecraftforge.event.server.ServerStoppedEvent;
import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.EventPriority;
@ -28,6 +31,7 @@ import org.embeddedt.modernfix.forge.ModernFixConfig;
import org.embeddedt.modernfix.forge.classloading.ClassLoadHack; import org.embeddedt.modernfix.forge.classloading.ClassLoadHack;
import org.embeddedt.modernfix.forge.classloading.ModFileScanDataDeduplicator; import org.embeddedt.modernfix.forge.classloading.ModFileScanDataDeduplicator;
import org.embeddedt.modernfix.forge.config.ConfigFixer; import org.embeddedt.modernfix.forge.config.ConfigFixer;
import org.embeddedt.modernfix.forge.config.NightConfigFixer;
import org.embeddedt.modernfix.forge.packet.PacketHandler; import org.embeddedt.modernfix.forge.packet.PacketHandler;
import org.embeddedt.modernfix.forge.registry.ObjectHolderClearer; import org.embeddedt.modernfix.forge.registry.ObjectHolderClearer;
@ -36,6 +40,7 @@ import java.util.List;
@Mod(ModernFix.MODID) @Mod(ModernFix.MODID)
public class ModernFixForge { public class ModernFixForge {
private static ModernFix commonMod; private static ModernFix commonMod;
public static boolean launchDone = false;
public ModernFixForge() { public ModernFixForge() {
commonMod = new ModernFix(); commonMod = new ModernFix();
@ -52,6 +57,18 @@ public class ModernFixForge {
ConfigFixer.replaceConfigHandlers(); ConfigFixer.replaceConfigHandlers();
} }
@SubscribeEvent
public void onCommandRegister(RegisterCommandsEvent event) {
for(String name : new String[] { "mfsrc"}) {
event.getDispatcher().register(LiteralArgumentBuilder.<CommandSourceStack>literal(name)
.requires(source -> source.hasPermission(3))
.executes(context -> {
NightConfigFixer.runReloads();
return 1;
}));
}
}
@SubscribeEvent @SubscribeEvent
public void onDatapackSync(OnDatapackSyncEvent event) { public void onDatapackSync(OnDatapackSyncEvent event) {
if(event.getPlayer() != null) { if(event.getPlayer() != null) {

View File

@ -9,6 +9,7 @@ import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.duck.IExtendedModelBakery; import org.embeddedt.modernfix.duck.IExtendedModelBakery;
import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider; import org.embeddedt.modernfix.dynamicresources.DynamicBakedModelProvider;
import org.embeddedt.modernfix.forge.dynresources.IModelBakerImpl;
import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
@ -19,16 +20,23 @@ import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;
import java.util.function.Function; import java.util.function.Function;
@Mixin(ModelBakery.ModelBakerImpl.class) @Mixin(ModelBakery.ModelBakerImpl.class)
public abstract class ModelBakerImplMixin { public abstract class ModelBakerImplMixin implements IModelBakerImpl {
private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading"); private static final boolean debugDynamicModelLoading = Boolean.getBoolean("modernfix.debugDynamicModelLoading");
@Shadow @Final private ModelBakery field_40571; @Shadow @Final private ModelBakery field_40571;
@Shadow public abstract UnbakedModel getModel(ResourceLocation arg); @Shadow public abstract UnbakedModel getModel(ResourceLocation arg);
private boolean mfix$ignoreCache = false;
@Override
public void mfix$ignoreCache() {
mfix$ignoreCache = true;
}
@Inject(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true, remap = false) @Inject(method = "bake(Lnet/minecraft/resources/ResourceLocation;Lnet/minecraft/client/resources/model/ModelState;Ljava/util/function/Function;)Lnet/minecraft/client/resources/model/BakedModel;", at = @At("HEAD"), cancellable = true, remap = false)
public void getOrLoadBakedModelDynamic(ResourceLocation arg, ModelState arg2, Function<Material, TextureAtlasSprite> textureGetter, CallbackInfoReturnable<BakedModel> cir) { public void getOrLoadBakedModelDynamic(ResourceLocation arg, ModelState arg2, Function<Material, TextureAtlasSprite> textureGetter, CallbackInfoReturnable<BakedModel> cir) {
ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(arg, arg2.getRotation(), arg2.isUvLocked()); ModelBakery.BakedCacheKey key = new ModelBakery.BakedCacheKey(arg, arg2.getRotation(), arg2.isUvLocked());
BakedModel existing = this.field_40571.bakedCache.get(key); BakedModel existing = mfix$ignoreCache ? null : this.field_40571.bakedCache.get(key);
if (existing != null) { if (existing != null) {
cir.setReturnValue(existing); cir.setReturnValue(existing);
} else { } else {

View File

@ -146,6 +146,14 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
return ImmutableList.of(); return ImmutableList.of();
} }
/**
* Make a copy of the top-level model list to avoid CME if more models get loaded here.
*/
@Redirect(method = "<init>", at = @At(value = "INVOKE", target = "Ljava/util/Map;values()Ljava/util/Collection;", ordinal = 0))
private Collection<?> copyTopLevelModelList(Map<?, ?> map) {
return new ArrayList<>(map.values());
}
private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter; private BiFunction<ResourceLocation, Material, TextureAtlasSprite> textureGetter;
@Inject(method = "bakeModels", at = @At("HEAD")) @Inject(method = "bakeModels", at = @At("HEAD"))
@ -266,7 +274,7 @@ public abstract class ModelBakeryMixin implements IExtendedModelBakery {
public BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state) { public BakedModel bakeDefault(ResourceLocation modelLocation, ModelState state) {
ModelBakery self = (ModelBakery) (Object) this; ModelBakery self = (ModelBakery) (Object) this;
ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation); ModelBaker theBaker = self.new ModelBakerImpl(textureGetter, modelLocation);
return theBaker.bake(modelLocation, state); return theBaker.bake(modelLocation, state, theBaker.getModelTextureGetter());
} }
@Override @Override

View File

@ -1,20 +1,28 @@
package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources.ctm; package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources.ctm;
import com.google.common.collect.Multimap;
import com.google.common.collect.Sets;
import com.mojang.datafixers.util.Pair; import com.mojang.datafixers.util.Pair;
import net.minecraft.client.Minecraft;
import net.minecraft.client.renderer.texture.TextureAtlasSprite;
import net.minecraft.client.resources.model.*; import net.minecraft.client.resources.model.*;
import net.minecraft.resources.ResourceLocation; import net.minecraft.resources.ResourceLocation;
import org.embeddedt.modernfix.ModernFixClient; import org.embeddedt.modernfix.ModernFixClient;
import org.embeddedt.modernfix.annotation.ClientOnlyMixin; import org.embeddedt.modernfix.annotation.ClientOnlyMixin;
import org.embeddedt.modernfix.annotation.RequiresMod; import org.embeddedt.modernfix.annotation.RequiresMod;
import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration; import org.embeddedt.modernfix.api.entrypoint.ModernFixClientIntegration;
import org.embeddedt.modernfix.api.helpers.ModelHelpers; import org.embeddedt.modernfix.forge.dynresources.IModelBakerImpl;
import org.spongepowered.asm.mixin.Final;
import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Shadow;
import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo;
import team.chisel.ctm.CTM; import team.chisel.ctm.CTM;
import team.chisel.ctm.api.model.IModelCTM;
import team.chisel.ctm.client.mixin.ModelBakerImplAccessor;
import team.chisel.ctm.client.model.AbstractCTMBakedModel; import team.chisel.ctm.client.model.AbstractCTMBakedModel;
import team.chisel.ctm.client.model.ModelCTM;
import team.chisel.ctm.client.texture.IMetadataSectionCTM; import team.chisel.ctm.client.texture.IMetadataSectionCTM;
import team.chisel.ctm.client.util.ResourceUtil; import team.chisel.ctm.client.util.ResourceUtil;
import team.chisel.ctm.client.util.TextureMetadataHandler; import team.chisel.ctm.client.util.TextureMetadataHandler;
@ -22,20 +30,23 @@ import team.chisel.ctm.client.util.TextureMetadataHandler;
import javax.annotation.Nonnull; import javax.annotation.Nonnull;
import java.io.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.function.Function;
@Mixin(TextureMetadataHandler.class) @Mixin(TextureMetadataHandler.class)
@RequiresMod("ctm") @RequiresMod("ctm")
@ClientOnlyMixin @ClientOnlyMixin
public abstract class TextureMetadataHandlerMixin implements ModernFixClientIntegration { public abstract class TextureMetadataHandlerMixin implements ModernFixClientIntegration {
@Shadow @Nonnull protected abstract BakedModel wrap(ResourceLocation loc, UnbakedModel model, BakedModel object, ModelBaker loader) throws IOException; @Shadow @Nonnull protected abstract BakedModel wrap(UnbakedModel model, BakedModel object) throws IOException;
@Shadow @Final public static Multimap<ResourceLocation, Material> TEXTURES_SCRAPED;
@Inject(method = "<init>", at = @At("RETURN")) @Inject(method = "<init>", at = @At("RETURN"))
private void subscribeDynamic(CallbackInfo ci) { private void subscribeDynamic(CallbackInfo ci) {
ModernFixClient.CLIENT_INTEGRATIONS.add(this); ModernFixClient.CLIENT_INTEGRATIONS.add(this);
} }
@Inject(method = "onModelBake", at = @At("HEAD"), cancellable = true, remap = false) @Inject(method = { "onModelBake(Lnet/minecraftforge/client/event/ModelEvent$ModifyBakingResult;)V", "onModelBake(Lnet/minecraftforge/client/event/ModelEvent$BakingCompleted;)V" }, at = @At("HEAD"), cancellable = true, remap = false)
private void noIteration(CallbackInfo ci) { private void noIteration(CallbackInfo ci) {
ci.cancel(); ci.cancel();
} }
@ -59,8 +70,7 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
continue; continue;
} }
// TODO port Collection<Material> textures = Sets.newHashSet(TEXTURES_SCRAPED.get(dep));
Collection<Material> textures = Collections.emptyList(); // model.getMaterials(event.getModelLoader()::getModel, errors);
Collection<ResourceLocation> newDependencies = model.getDependencies(); Collection<ResourceLocation> newDependencies = model.getDependencies();
for (Material tex : textures) { for (Material tex : textures) {
IMetadataSectionCTM meta = null; IMetadataSectionCTM meta = null;
@ -82,7 +92,8 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
} }
if (shouldWrap) { if (shouldWrap) {
try { try {
baked = wrap(rl, rootModel, baked, ModelHelpers.adaptBakery(bakery)); baked = wrap(rootModel, baked);
handleInit(rl, baked, bakery);
dependencies.clear(); dependencies.clear();
} catch (IOException e) { } catch (IOException e) {
CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e); CTM.logger.error("Could not wrap model " + rl + ". Aborting...", e);
@ -91,4 +102,23 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte
} }
return baked; return baked;
} }
private void handleInit(ResourceLocation key, BakedModel wrappedModel, ModelBakery bakery) {
if(wrappedModel instanceof AbstractCTMBakedModel baked) {
IModelCTM var10 = baked.getModel();
if (var10 instanceof ModelCTM ctmModel) {
if (!ctmModel.isInitialized()) {
Function<Material, TextureAtlasSprite> spriteGetter = (m) -> {
return Minecraft.getInstance().getModelManager().getAtlas(m.atlasLocation()).getSprite(m.texture());
};
ModelBakery.ModelBakerImpl baker = ModelBakerImplAccessor.createImpl(bakery, ($, m) -> {
return spriteGetter.apply(m);
}, key);
// bypass bakedCache so that dependent models get re-baked and thus retrieve their sprites again
((IModelBakerImpl)baker).mfix$ignoreCache();
ctmModel.bake(baker, spriteGetter, BlockModelRotation.X0_Y0, key);
}
}
}
}
} }

View File

@ -22,8 +22,10 @@ import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.server.ServerLifecycleHooks; import net.minecraftforge.server.ServerLifecycleHooks;
import org.embeddedt.modernfix.api.constants.IntegrationConstants; import org.embeddedt.modernfix.api.constants.IntegrationConstants;
import org.embeddedt.modernfix.core.ModernFixMixinPlugin; import org.embeddedt.modernfix.core.ModernFixMixinPlugin;
import org.embeddedt.modernfix.forge.classloading.ATInjector;
import org.embeddedt.modernfix.forge.classloading.FastAccessTransformerList; import org.embeddedt.modernfix.forge.classloading.FastAccessTransformerList;
import org.embeddedt.modernfix.forge.config.NightConfigFixer; import org.embeddedt.modernfix.forge.config.NightConfigFixer;
import org.embeddedt.modernfix.forge.init.ModernFixForge;
import org.embeddedt.modernfix.forge.packet.PacketHandler; import org.embeddedt.modernfix.forge.packet.PacketHandler;
import org.embeddedt.modernfix.platform.ModernFixPlatformHooks; import org.embeddedt.modernfix.platform.ModernFixPlatformHooks;
import org.embeddedt.modernfix.spark.SparkLaunchProfiler; import org.embeddedt.modernfix.spark.SparkLaunchProfiler;
@ -92,6 +94,9 @@ public class ModernFixPlatformHooksImpl implements ModernFixPlatformHooks {
} }
public void injectPlatformSpecificHacks() { public void injectPlatformSpecificHacks() {
if(!isEarlyLoadingNormally() && ModernFixMixinPlugin.instance.isOptionEnabled("bugfix.forge_at_inject_error.ATInjector")) {
ATInjector.injectModATs();
}
FastAccessTransformerList.attemptReplace(); FastAccessTransformerList.attemptReplace();
/* https://github.com/FabricMC/Mixin/pull/99 */ /* https://github.com/FabricMC/Mixin/pull/99 */
@ -164,6 +169,7 @@ public class ModernFixPlatformHooksImpl implements ModernFixPlatformHooks {
if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) { if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnForge")) {
CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler"); CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.stop("launch"), "Failed to stop profiler");
} }
ModernFixForge.launchDone = true;
} }
public String getPlatformName() { public String getPlatformName() {

View File

@ -11,7 +11,7 @@ forge_version=1.20.1-47.0.14
refined_storage_version=4392788 refined_storage_version=4392788
jei_version=13.1.0.2 jei_version=13.1.0.2
rei_version=11.0.597 rei_version=11.0.597
ctm_version=1.19.2-1.1.7+11 ctm_version=1.20.1-1.1.8+4
kubejs_version=1902.6.0-build.142 kubejs_version=1902.6.0-build.142
rhino_version=1902.2.2-build.268 rhino_version=1902.2.2-build.268
supported_minecraft_versions=23w31a supported_minecraft_versions=23w31a