diff --git a/.gitignore b/.gitignore index c70bdc40..f427fdd2 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ libs media classes/ .architectury-transformer/ +fabric/fabricloader.log +fabric/test_run # Changelog CHANGELOG.md diff --git a/build.gradle b/build.gradle index 8be1557e..49bb7200 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id "architectury-plugin" version "3.4-SNAPSHOT" - id "dev.architectury.loom" version "1.1-SNAPSHOT" apply false + id "dev.architectury.loom" version "1.2-SNAPSHOT" apply false id "maven-publish" id 'com.matthewprenger.cursegradle' version '1.4.0' apply false id 'com.palantir.git-version' version '1.0.0' @@ -93,7 +93,7 @@ allprojects { } } -subprojects { +configure(subprojects.findAll {it.name == "common" || it.name == "forge" || it.name == "fabric"}) { apply plugin: "dev.architectury.loom" loom { diff --git a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java index 576159a3..db024b63 100644 --- a/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java +++ b/common/src/main/java/org/embeddedt/modernfix/common/mixin/perf/reduce_blockstate_cache_rebuilds/BlockStateBaseMixin.java @@ -25,6 +25,11 @@ public abstract class BlockStateBaseMixin implements IBlockState { cacheInvalid = true; } + @Override + public boolean isCacheInvalid() { + return cacheInvalid; + } + private BlockBehaviour.BlockStateBase.Cache generateCache(BlockBehaviour.BlockStateBase base) { if(cacheInvalid) { // Ensure that only one block's cache is built at a time diff --git a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java index cc515a34..a38c6d14 100644 --- a/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java +++ b/common/src/main/java/org/embeddedt/modernfix/core/config/ModernFixEarlyConfig.java @@ -311,19 +311,21 @@ public class ModernFixEarlyConfig { public static ModernFixEarlyConfig load(File file) { ModernFixEarlyConfig config = new ModernFixEarlyConfig(file); Properties props = new Properties(); - if(file.exists()) { - try (FileInputStream fin = new FileInputStream(file)){ - props.load(fin); - } catch (IOException e) { - throw new RuntimeException("Could not load config file", e); + if(!Boolean.getBoolean("modernfix.ignoreConfigForTesting")) { + if(file.exists()) { + try (FileInputStream fin = new FileInputStream(file)){ + props.load(fin); + } catch (IOException e) { + throw new RuntimeException("Could not load config file", e); + } + config.readProperties(props); } - config.readProperties(props); - } - try { - config.save(); - } catch (IOException e) { - LOGGER.warn("Could not write configuration file", e); + try { + config.save(); + } catch (IOException e) { + LOGGER.warn("Could not write configuration file", e); + } } return config; diff --git a/common/src/main/java/org/embeddedt/modernfix/duck/IBlockState.java b/common/src/main/java/org/embeddedt/modernfix/duck/IBlockState.java index d396f128..ea38b50e 100644 --- a/common/src/main/java/org/embeddedt/modernfix/duck/IBlockState.java +++ b/common/src/main/java/org/embeddedt/modernfix/duck/IBlockState.java @@ -3,4 +3,5 @@ package org.embeddedt.modernfix.duck; public interface IBlockState { void clearCache(); + boolean isCacheInvalid(); } diff --git a/common/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java b/common/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java index 2f458dca..8efaa4f1 100644 --- a/common/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java +++ b/common/src/main/java/org/embeddedt/modernfix/util/ClassInfoManager.java @@ -49,6 +49,8 @@ public class ClassInfoManager { if(entry.getKey().equals("java/lang/Object")) return false; ClassInfo mixinClz = entry.getValue(); + if(mixinClz == null) + return true; try { if(mixinClz.isMixin()) { // clear classNode in MixinInfo.State diff --git a/fabric/build.gradle b/fabric/build.gradle index bc893c61..98baf4d8 100644 --- a/fabric/build.gradle +++ b/fabric/build.gradle @@ -1,5 +1,6 @@ plugins { id "com.github.johnrengelman.shadow" version "7.1.2" + id 'com.adarshr.test-logger' version '3.2.0' } architectury { @@ -22,10 +23,16 @@ configurations { include.extendsFrom modIncludeImplementation modImplementation.extendsFrom modIncludeImplementation + + testAgent { + canBeConsumed = false + } } dependencies { modImplementation "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + testImplementation "net.fabricmc:fabric-loader-junit:${rootProject.fabric_loader_version}" + modIncludeImplementation(fabricApi.module("fabric-api-base", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-lifecycle-events-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } modIncludeImplementation(fabricApi.module("fabric-screen-api-v1", rootProject.fabric_api_version)) { exclude group: 'net.fabricmc', module: 'fabric-loader' } @@ -40,7 +47,33 @@ dependencies { // modApi "me.shedaniel:architectury-fabric:${rootProject.architectury_version}" common(project(path: ":common", configuration: "namedElements")) { transitive false } - shadowCommon(project(path: ":common", configuration: "transformProductionFabric")) { transitive false } + testImplementation(shadowCommon(project(path: ":common", configuration: "transformProductionFabric"))) { transitive false } + + testImplementation(platform("org.junit:junit-bom:${project.junit_version}")) + testImplementation("org.junit.jupiter:junit-jupiter-api") + testImplementation("org.junit.jupiter:junit-jupiter-params") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + testImplementation("org.junit.platform:junit-platform-launcher") + testImplementation("org.assertj:assertj-core:3.19.0") + testImplementation("com.google.guava:guava-testlib:21.0") + testImplementation("org.mockito:mockito-junit-jupiter:5.3.1") + + testAgent(project("path": ":test_agent", "configuration": "agentJar")) +} + +test { + useJUnitPlatform() + def runDir = file('test_run') + doFirst() { + runDir.mkdir() + } + workingDir = runDir + systemProperty 'modernfix.ignoreConfigForTesting', 'true' + + // inject our custom agent to fix #817 + FileCollection agentFile = configurations.getByName("testAgent") + jvmArgs "-javaagent:${agentFile.singleFile.absolutePath}" + dependsOn(agentFile) } processResources { @@ -55,18 +88,18 @@ shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" + archiveClassifier.set("dev-shadow") } remapJar { injectAccessWidener = true input.set shadowJar.archiveFile dependsOn shadowJar - classifier null + archiveClassifier.set(null) } jar { - classifier "dev" + archiveClassifier.set("dev") } components.java { diff --git a/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java index 978aaa85..92352b52 100644 --- a/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java +++ b/fabric/src/main/java/org/embeddedt/modernfix/ModernFixPreLaunchFabric.java @@ -9,6 +9,10 @@ import org.embeddedt.modernfix.util.CommonModUtil; public class ModernFixPreLaunchFabric implements PreLaunchEntrypoint { @Override public void onPreLaunch() { + if(ModernFixMixinPlugin.instance == null) { + System.err.println("Mixin plugin not loaded yet"); + return; + } if(ModernFixMixinPlugin.instance.isOptionEnabled("feature.spark_profile_launch.OnFabric")) { CommonModUtil.runWithoutCrash(() -> SparkLaunchProfiler.start("launch"), "Failed to start profiler"); } diff --git a/fabric/src/test/java/net/minecraft/world/level/block/state/BlockStateCacheTest.java b/fabric/src/test/java/net/minecraft/world/level/block/state/BlockStateCacheTest.java new file mode 100644 index 00000000..c6884fe2 --- /dev/null +++ b/fabric/src/test/java/net/minecraft/world/level/block/state/BlockStateCacheTest.java @@ -0,0 +1,58 @@ +package net.minecraft.world.level.block.state; + +import net.minecraft.core.BlockPos; +import net.minecraft.world.level.EmptyBlockGetter; +import net.minecraft.world.level.block.Blocks; +import org.embeddedt.modernfix.duck.IBlockState; +import org.embeddedt.modernfix.testing.util.BootstrapMinecraft; +import org.junit.jupiter.api.*; + +import static org.junit.jupiter.api.Assertions.*; + +@BootstrapMinecraft +@TestMethodOrder(MethodOrderer.OrderAnnotation.class) +public class BlockStateCacheTest { + @BeforeEach + public void rebuildCache() { + Blocks.rebuildCache(); + } + + /** + * Initially, the cache should be invalid, and null. + */ + @Test + @Order(1) + public void testCacheNullInitially() { + BlockState stoneBlock = Blocks.STONE.defaultBlockState(); + assertTrue(((IBlockState)stoneBlock).isCacheInvalid()); + assertNull(stoneBlock.cache); + } + + /** + * When an API that needs the cache is called, it should be built and the invalid flag + * becomes false. + */ + @Test + @Order(2) + public void testCacheBuiltByRequest() { + BlockState stoneBlock = Blocks.STONE.defaultBlockState(); + stoneBlock.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); + assertFalse(((IBlockState)stoneBlock).isCacheInvalid()); + assertNotNull(stoneBlock.cache); + } + + /** + * When a second rebuild occurs, the invalid flag should be set to true, but the old cache + * is not set to null, in order to prevent NPEs if a second thread is accessing the cache + * when this takes place. + */ + @Test + @Order(3) + public void testCacheInvalidatedByLateRebuild() { + BlockState stoneBlock = Blocks.STONE.defaultBlockState(); + stoneBlock.getCollisionShape(EmptyBlockGetter.INSTANCE, BlockPos.ZERO); + Blocks.rebuildCache(); + assertTrue(((IBlockState)stoneBlock).isCacheInvalid()); + assertNotNull(stoneBlock.cache); + } +} diff --git a/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraft.java b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraft.java new file mode 100644 index 00000000..a1710398 --- /dev/null +++ b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraft.java @@ -0,0 +1,14 @@ +package org.embeddedt.modernfix.testing.util; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.junit.jupiter.api.extension.ExtendWith; + +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@ExtendWith({ BootstrapMinecraftExtension.class }) +public @interface BootstrapMinecraft { +} diff --git a/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java new file mode 100644 index 00000000..a2417e3c --- /dev/null +++ b/fabric/src/test/java/org/embeddedt/modernfix/testing/util/BootstrapMinecraftExtension.java @@ -0,0 +1,24 @@ +package org.embeddedt.modernfix.testing.util; + +import net.minecraft.DetectedVersion; +import net.minecraft.server.Bootstrap; +import org.junit.jupiter.api.extension.AfterAllCallback; +import org.junit.jupiter.api.extension.BeforeAllCallback; +import org.junit.jupiter.api.extension.Extension; +import org.junit.jupiter.api.extension.ExtensionContext; + +/** + * Simple extension to run vanilla bootstrap, inspired by AE2. + */ +public class BootstrapMinecraftExtension implements Extension, BeforeAllCallback, AfterAllCallback { + @Override + public void beforeAll(ExtensionContext context) throws Exception { + DetectedVersion.tryDetectVersion(); + Bootstrap.bootStrap(); + } + + @Override + public void afterAll(ExtensionContext context) throws Exception { + + } +} diff --git a/forge/build.gradle b/forge/build.gradle index 898c04c1..e58467d6 100644 --- a/forge/build.gradle +++ b/forge/build.gradle @@ -62,17 +62,17 @@ shadowJar { exclude "architectury.common.json" configurations = [project.configurations.shadowCommon] - classifier "dev-shadow" + archiveClassifier.set("dev-shadow") } remapJar { input.set shadowJar.archiveFile dependsOn shadowJar - classifier null + archiveClassifier.set(null) } jar { - classifier "dev" + archiveClassifier.set("dev") manifest { attributes([ "Specification-Title" : "modernfix", diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java b/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java new file mode 100644 index 00000000..c2b3de68 --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/dynresources/ModelBakeEventHelper.java @@ -0,0 +1,82 @@ +package org.embeddedt.modernfix.forge.dynresources; + +import com.google.common.collect.ForwardingMap; +import com.google.common.collect.Sets; +import com.google.common.graph.GraphBuilder; +import com.google.common.graph.MutableGraph; +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.world.item.Item; +import net.minecraft.world.level.block.Block; +import net.minecraft.world.level.block.state.BlockState; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.forgespi.language.IModInfo; +import net.minecraftforge.registries.ForgeRegistries; +import org.embeddedt.modernfix.dynamicresources.ModelLocationCache; +import org.jetbrains.annotations.Nullable; + +import java.util.HashSet; +import java.util.Map; +import java.util.Optional; +import java.util.Set; + +/** + * Stores a list of all known default block/item models in the game, and provides a namespaced version + * of the model registry that emulates vanilla keySet behavior. + */ +public class ModelBakeEventHelper { + private final Map modelRegistry; + private final Set topLevelModelLocations; + private final MutableGraph dependencyGraph; + public ModelBakeEventHelper(Map modelRegistry) { + this.modelRegistry = modelRegistry; + this.topLevelModelLocations = new HashSet<>(modelRegistry.keySet()); + for(Block block : ForgeRegistries.BLOCKS) { + for(BlockState state : block.getStateDefinition().getPossibleStates()) { + topLevelModelLocations.add(ModelLocationCache.get(state)); + } + } + for(Item item : ForgeRegistries.ITEMS) { + topLevelModelLocations.add(ModelLocationCache.get(item)); + } + this.dependencyGraph = GraphBuilder.undirected().build(); + ModList.get().forEachModContainer((id, mc) -> { + this.dependencyGraph.addNode(id); + }); + for(String id : this.dependencyGraph.nodes()) { + Optional mContainer = ModList.get().getModContainerById(id); + if(mContainer.isPresent()) { + for(IModInfo.ModVersion version : mContainer.get().getModInfo().getDependencies()) { + this.dependencyGraph.putEdge(id, version.getModId()); + } + } + } + } + + public Map wrapRegistry(String modId) { + final Set modIdsToInclude = new HashSet<>(); + modIdsToInclude.add(modId); + try { + modIdsToInclude.addAll(this.dependencyGraph.adjacentNodes(modId)); + } catch(IllegalArgumentException ignored) { /* sanity check */ } + modIdsToInclude.remove("minecraft"); + Set ourModelLocations = Sets.filter(this.topLevelModelLocations, loc -> modIdsToInclude.contains(loc.getNamespace())); + return new ForwardingMap() { + @Override + protected Map delegate() { + return modelRegistry; + } + + @Override + public Set keySet() { + return ourModelLocations; + } + + @Override + public boolean containsKey(@Nullable Object key) { + return ourModelLocations.contains(key) || super.containsKey(key); + } + }; + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java new file mode 100644 index 00000000..f1aba5bc --- /dev/null +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ForgeHooksClientMixin.java @@ -0,0 +1,42 @@ +package org.embeddedt.modernfix.forge.mixin.perf.dynamic_resources; + +import net.minecraft.client.resources.model.BakedModel; +import net.minecraft.resources.ResourceLocation; +import net.minecraftforge.client.ForgeHooksClient; +import net.minecraftforge.client.event.ModelBakeEvent; +import net.minecraftforge.eventbus.api.Event; +import net.minecraftforge.fml.ModContainer; +import net.minecraftforge.fml.ModList; +import net.minecraftforge.fml.ModLoader; +import net.minecraftforge.fml.util.ObfuscationReflectionHelper; +import org.embeddedt.modernfix.forge.dynresources.ModelBakeEventHelper; +import org.spongepowered.asm.mixin.Mixin; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.lang.reflect.Method; +import java.util.Map; + +@Mixin(ForgeHooksClient.class) +public class ForgeHooksClientMixin { + /** + * Generate a more realistic keySet that contains every item and block model location, to help with mod compat. + */ + @Redirect(method = "onModelBake", at = @At(value = "INVOKE", target = "Lnet/minecraftforge/fml/ModLoader;postEvent(Lnet/minecraftforge/eventbus/api/Event;)V")) + private static void postNamespacedKeySetEvent(ModLoader loader, Event event) { + if(!ModLoader.isLoadingStateValid()) + return; + ModelBakeEvent bakeEvent = ((ModelBakeEvent)event); + ModelBakeEventHelper helper = new ModelBakeEventHelper(bakeEvent.getModelRegistry()); + Method acceptEv = ObfuscationReflectionHelper.findMethod(ModContainer.class, "acceptEvent", Event.class); + ModList.get().forEachModContainer((id, mc) -> { + Map newRegistry = helper.wrapRegistry(id); + ModelBakeEvent postedEvent = new ModelBakeEvent(bakeEvent.getModelManager(), newRegistry, bakeEvent.getModelLoader()); + try { + acceptEv.invoke(mc, postedEvent); + } catch(ReflectiveOperationException e) { + e.printStackTrace(); + } + }); + } +} diff --git a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java index 24e9b925..560a47a3 100644 --- a/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java +++ b/forge/src/main/java/org/embeddedt/modernfix/forge/mixin/perf/dynamic_resources/ctm/TextureMetadataHandlerMixin.java @@ -12,7 +12,6 @@ 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.Redirect; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; import team.chisel.ctm.CTM; import team.chisel.ctm.client.model.AbstractCTMBakedModel; @@ -36,9 +35,9 @@ public abstract class TextureMetadataHandlerMixin implements ModernFixClientInte ModernFixClient.CLIENT_INTEGRATIONS.add(this); } - @Redirect(method = "onModelBake", at = @At(value = "INVOKE", target = "Lnet/minecraft/client/resources/model/BakedModel;isCustomRenderer()Z")) - private boolean checkModelValid(BakedModel model) { - return model == null || model.isCustomRenderer(); + @Inject(method = "onModelBake", at = @At("HEAD"), cancellable = true, remap = false) + private void noIteration(CallbackInfo ci) { + ci.cancel(); } @Override diff --git a/gradle.properties b/gradle.properties index 4a9f8ad7..da5c388d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,6 +1,8 @@ # Done to increase the memory available to gradle. org.gradle.jvmargs=-Xmx2G +junit_version=5.10.0-M1 + mod_id=modernfix minecraft_version=1.18.2 enabled_platforms=fabric,forge @@ -15,7 +17,7 @@ kubejs_version=1802.5.5-build.569 rhino_version=1802.2.1-build.255 supported_minecraft_versions=1.18.2 -fabric_loader_version=0.14.18 +fabric_loader_version=0.14.21 fabric_api_version=0.76.0+1.18.2 modmenu_version=3.2.5 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index aa991fce..fae08049 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/settings.gradle b/settings.gradle index 8a77caf9..4afff011 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,6 +7,7 @@ pluginManagement { } } +include("test_agent") include("common") include("fabric") include("forge") diff --git a/test_agent/build.gradle b/test_agent/build.gradle new file mode 100644 index 00000000..2141b87d --- /dev/null +++ b/test_agent/build.gradle @@ -0,0 +1,86 @@ +plugins { + //id 'com.github.johnrengelman.shadow' version '7.1.2' + id 'java' +} + +group 'org.embeddedt' +archivesBaseName = 'modernfix-test-agent' +version '1.0' + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +repositories { + mavenCentral() + mavenLocal() + maven { + name = 'forge' + url = 'https://maven.minecraftforge.net/' + } +} + +/* + +shadowJar { + relocate 'net.bytebuddy.agent', 'org.embeddedt.modernfix.testing.shadow.bytebuddyagent' + relocate 'org.objectweb.asm', 'org.embeddedt.modernfix.testing.shadow.asm' +} + + + +shadowJar { + project.configurations.implementation.canBeResolved = true + configurations = [project.configurations.implementation] +} + */ +dependencies { + compileOnly "net.fabricmc:fabric-loader:${rootProject.fabric_loader_version}" + implementation "org.ow2.asm:asm-tree:9.1" + implementation "org.ow2.asm:asm-commons:9.1" + implementation "org.ow2.asm:asm-util:9.1" + + //implementation('net.bytebuddy:byte-buddy-agent:1.12.22') +} + +tasks.withType(JavaCompile) { + // ensure that the encoding is set to UTF-8, no matter what the system default is + // this fixes some edge cases with special characters not displaying correctly + // see http://yodaconditions.net/blog/fix-for-java-file-encoding-problems-with-gradle.html + // If Javadoc is generated, this must be specified in that task too. + options.encoding = "UTF-8" +} + +jar { + manifest { + attributes( + "Premain-Class": "org.embeddedt.modernfix.testing.Agent", + "Can-Redefine-Classes": false, + "Can-Set-Native-Method-Prefix": false + ) + } +} +/* + +shadowJar { + archiveBaseName.set('modernfix-test-agent') + archiveClassifier.set('') + archiveVersion.set('v1') +} + + */ + +configurations { + agentJar { + canBeConsumed = true + canBeResolved = false + } +} + +artifacts { + agentJar(jar) +} +/* +project.tasks.shadowJar.dependsOn build +defaultTasks 'shadowJar' + + */ \ No newline at end of file diff --git a/test_agent/src/main/java/org/embeddedt/modernfix/testing/Agent.java b/test_agent/src/main/java/org/embeddedt/modernfix/testing/Agent.java new file mode 100644 index 00000000..b7b69479 --- /dev/null +++ b/test_agent/src/main/java/org/embeddedt/modernfix/testing/Agent.java @@ -0,0 +1,61 @@ +package org.embeddedt.modernfix.testing; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import java.lang.instrument.ClassFileTransformer; +import java.lang.instrument.IllegalClassFormatException; +import java.lang.instrument.Instrumentation; +import java.security.ProtectionDomain; +import java.util.ListIterator; + +public class Agent { + /** + * Simple agent that transforms Fabric Loader to never mark game JARs as system libraries. + * + * Ugly, but usable workaround for issue #817 + * on the Loader bug tracker. + */ + public static void premain(String args, Instrumentation instrumentation) { + instrumentation.addTransformer(new ClassFileTransformer() { + @Override + public byte[] transform(ClassLoader loader, String className, Class classBeingRedefined, ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException { + if(className.equals("net/fabricmc/loader/impl/game/LibClassifier")) { + ClassNode node = new ClassNode(); + ClassReader reader = new ClassReader(classfileBuffer); + reader.accept(node, 0); + for(MethodNode m : node.methods) { + if(m.name.equals("")) { + ListIterator iter = m.instructions.iterator(); + int addMatches = 0; + while(iter.hasNext()) { + AbstractInsnNode n = iter.next(); + if(n instanceof MethodInsnNode) { + MethodInsnNode invokeNode = (MethodInsnNode)n; + if(invokeNode.name.equals("add") && invokeNode.owner.equals("java/util/Set") && invokeNode.desc.equals("(Ljava/lang/Object;)Z")) { + addMatches++; + if(addMatches == 2) { + iter.set(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/embeddedt/modernfix/testing/AgentHooks", "addLibraryWithCheck", "(Ljava/util/Set;Ljava/lang/Object;)Z", false)); + break; + } + } + } + } + } + } + ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES | ClassWriter.COMPUTE_MAXS); + node.accept(writer); + byte[] finalArray = writer.toByteArray(); + //dumpDebugClass(className, finalArray); + return finalArray; + } + return classfileBuffer; + } + }); + } +} diff --git a/test_agent/src/main/java/org/embeddedt/modernfix/testing/AgentHooks.java b/test_agent/src/main/java/org/embeddedt/modernfix/testing/AgentHooks.java new file mode 100644 index 00000000..05b8de12 --- /dev/null +++ b/test_agent/src/main/java/org/embeddedt/modernfix/testing/AgentHooks.java @@ -0,0 +1,17 @@ +package org.embeddedt.modernfix.testing; + +import java.nio.file.Path; +import java.util.Set; + +@SuppressWarnings("unused") +public class AgentHooks { + @SuppressWarnings({"unchecked", "rawtypes" }) + public static boolean addLibraryWithCheck(Set pathSet, Object path) { + boolean shouldAdd; + if(path instanceof Path) { + shouldAdd = !((Path)path).toString().contains("minecraft-merged"); + } else + shouldAdd = true; + return shouldAdd && pathSet.add(path); + } +}