diff options
author | Linnea Gräf <nea@nea.moe> | 2024-08-10 01:59:34 +0200 |
---|---|---|
committer | Linnea Gräf <nea@nea.moe> | 2024-08-12 21:02:44 +0200 |
commit | 3c7e6b6177de6ef3cff8a46bb1726466a299cdde (patch) | |
tree | 2ebc75e705b5422a68d5d7f04d88e3d8934cf02d | |
parent | 1606188d9ad65c66e9d873497ea3271dbdadaf77 (diff) | |
download | firmament-3c7e6b6177de6ef3cff8a46bb1726466a299cdde.tar.gz firmament-3c7e6b6177de6ef3cff8a46bb1726466a299cdde.tar.bz2 firmament-3c7e6b6177de6ef3cff8a46bb1726466a299cdde.zip |
Add indigo support to custom block textures
22 files changed, 689 insertions, 46 deletions
@@ -31,3 +31,8 @@ SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors" path = "**/*.gradle.kts" SPDX-License-Identifier = "CC0-1.0" SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"] + +[[annotations]] +path = ["**/META-INF/services/*"] +SPDX-License-Identifier = "CC0-1.0" +SPDX-FileCopyrightText = ["Linnea Gräf <nea@nea.moe>", "Firmament Contributors"] diff --git a/build.gradle.kts b/build.gradle.kts index 24b263e..12b8bc6 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -7,6 +7,7 @@ */ import moe.nea.licenseextractificator.LicenseDiscoveryTask +import net.fabricmc.loom.LoomGradleExtension import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { @@ -38,46 +39,49 @@ compileTestKotlin.kotlinOptions { jvmTarget = "21" } -repositories { - maven("https://maven.terraformersmc.com/releases/") - maven("https://maven.shedaniel.me") - maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") - maven("https://api.modrinth.com/maven") { - content { - includeGroup("maven.modrinth") - } - } - maven("https://repo.sleeping.town") { - content { - includeGroup("com.unascribed") - } - } - ivy("https://github.com/HotswapProjects/HotswapAgent/releases/download") { - patternLayout { - artifact("[revision]/[artifact]-[revision].[ext]") +allprojects { + repositories { + mavenCentral() + maven("https://maven.terraformersmc.com/releases/") + maven("https://maven.shedaniel.me") + maven("https://maven.fabricmc.net") + maven("https://pkgs.dev.azure.com/djtheredstoner/DevAuth/_packaging/public/maven/v1") + maven("https://api.modrinth.com/maven") { + content { + includeGroup("maven.modrinth") + } } - content { - includeGroup("virtual.github.hotswapagent") + maven("https://repo.sleeping.town") { + content { + includeGroup("com.unascribed") + } } - metadataSources { - artifact() + ivy("https://github.com/HotswapProjects/HotswapAgent/releases/download") { + patternLayout { + artifact("[revision]/[artifact]-[revision].[ext]") + } + content { + includeGroup("virtual.github.hotswapagent") + } + metadataSources { + artifact() + } } - } - maven("https://server.bbkr.space/artifactory/libs-release") - maven("https://repo.nea.moe/releases") - maven("https://maven.notenoughupdates.org/releases") - maven("https://repo.nea.moe/mirror") - maven("https://jitpack.io/") { - content { - includeGroupByRegex("(com|io)\\.github\\..+") - excludeModule("io.github.cottonmc", "LibGui") + maven("https://server.bbkr.space/artifactory/libs-release") + maven("https://repo.nea.moe/releases") + maven("https://maven.notenoughupdates.org/releases") + maven("https://repo.nea.moe/mirror") + maven("https://jitpack.io/") { + content { + includeGroupByRegex("(com|io)\\.github\\..+") + excludeModule("io.github.cottonmc", "LibGui") + } } + maven("https://repo.hypixel.net/repository/Hypixel/") + maven("https://maven.azureaaron.net/snapshots") + mavenLocal() } - maven( "https://repo.hypixel.net/repository/Hypixel/") - maven("https://maven.azureaaron.net/snapshots") - mavenLocal() } - kotlin { sourceSets.all { languageSettings { @@ -124,6 +128,8 @@ dependencies { modImplementation(libs.moulconfig) modImplementation(libs.manninghamMills) modCompileOnly(libs.explosiveenhancement) + compileOnly(project(":javaplugin")) + annotationProcessor(project(":javaplugin")) include(libs.manninghamMills) include(libs.moulconfig) @@ -209,8 +215,21 @@ loom { } tasks.withType<JavaCompile> { + this.sourceCompatibility = "21" + this.targetCompatibility = "21" options.encoding = "UTF-8" - options.release.set(21) + val module = "ALL-UNNAMED" + options.forkOptions.jvmArgs!!.addAll(listOf( + "--add-exports=jdk.compiler/com.sun.tools.javac.util=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=$module", + )) + options.isFork = true + afterEvaluate { + options.compilerArgs.add("-Xplugin:IntermediaryNameReplacement mappingFile=${LoomGradleExtension.get(project).mappingsFile.absolutePath} sourceNs=named") + } } tasks.jar { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 80731a7..d4820e3 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,7 +71,7 @@ runtime_required = [ runtime_optional = [ "devauth", "freecammod", - "sodium", +# "sodium", # "qolify", # "citresewn", # "ncr", diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 4c78103..0d93904 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -4,6 +4,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/javaplugin/build.gradle.kts b/javaplugin/build.gradle.kts new file mode 100644 index 0000000..9707639 --- /dev/null +++ b/javaplugin/build.gradle.kts @@ -0,0 +1,16 @@ +plugins { + java +} +dependencies { + implementation("net.fabricmc:stitch:0.6.2") +} +tasks.withType(JavaCompile::class) { + val module = "ALL-UNNAMED" + options.compilerArgs.addAll(listOf( + "--add-exports=jdk.compiler/com.sun.tools.javac.util=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.comp=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.tree=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.api=$module", + "--add-exports=jdk.compiler/com.sun.tools.javac.code=$module", + )) +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/InitReplacer.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/InitReplacer.java new file mode 100644 index 0000000..a0d28ab --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/InitReplacer.java @@ -0,0 +1,73 @@ +package moe.nea.firmament.javaplugin; + +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.VariableTree; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.List; +import com.sun.tools.javac.util.Names; + +public class InitReplacer extends TreeScanner<Void, Void> { + private final MappingTree mappingTree; + private final TreeMaker treeMaker; + private final Names names; + private final IntermediaryNameResolutionTask plugin; + private Symbol.ClassSymbol classTree; + private CompilationUnitTree compilationUnitTree; + + public InitReplacer(MappingTree mappingTree, IntermediaryNameResolutionTask plugin) { + this.mappingTree = mappingTree; + this.treeMaker = plugin.treeMaker; + this.names = plugin.names; + this.plugin = plugin; + } + + @Override + public Void visitClass(ClassTree node, Void unused) { + this.classTree = plugin.utils.getSymbol(node); + return super.visitClass(node, unused); + } + + @Override + public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { + this.compilationUnitTree = node; + return super.visitCompilationUnit(node, unused); + } + + @Override + public Void visitVariable(VariableTree node, Void unused) { + var annotation = node + .getModifiers().getAnnotations() + .stream() + .filter(it -> it.getAnnotationType().toString().equals("IntermediaryName")) // Crazy type-safety! + .findAny(); + if (annotation.isEmpty()) + return super.visitVariable(node, unused); + var jcAnnotation = (JCTree.JCAnnotation) annotation.get(); + var jcNode = (JCTree.JCVariableDecl) node; + if (node.getInitializer() != null) { + plugin.utils.reportError( + compilationUnitTree.getSourceFile(), + jcNode.getInitializer(), + "Providing an initializer for a variable is illegal for @IntermediaryName annotated fields" + ); + return super.visitVariable(node, unused); + } + var target = plugin.utils.getAnnotationValue(jcAnnotation, "value"); + var targetClass = plugin.utils.resolveClassLiteralExpression(target).tsym.flatName().toString(); + var intermediaryClass = mappingTree.resolveClassToIntermediary(targetClass); + var remapper = treeMaker.Select(treeMaker.This(classTree.type), names.fromString("remapper")); + var remappingCall = treeMaker.Apply( + List.nil(), + treeMaker.Select(remapper, names.fromString("mapClassName")), + List.of(treeMaker.Literal("intermediary"), + treeMaker.Literal(intermediaryClass))); + jcNode.init = remappingCall; + jcNode.mods.annotations = List.filter(jcNode.mods.annotations, jcAnnotation); + return super.visitVariable(node, unused); + } + +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryMethodReplacer.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryMethodReplacer.java new file mode 100644 index 0000000..d3040b7 --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryMethodReplacer.java @@ -0,0 +1,72 @@ +package moe.nea.firmament.javaplugin; + +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.MethodInvocationTree; +import com.sun.source.util.TreeScanner; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.List; + +import javax.tools.JavaFileObject; + +public class IntermediaryMethodReplacer extends TreeScanner<Void, Void> { + private final MappingTree mappings; + private final IntermediaryNameResolutionTask plugin; + private JavaFileObject sourceFile; + private CompilationUnitTree compilationUnit; + + public IntermediaryMethodReplacer(MappingTree mappings, IntermediaryNameResolutionTask plugin) { + this.mappings = mappings; + this.plugin = plugin; + } + + + @Override + public Void visitCompilationUnit(CompilationUnitTree node, Void unused) { + sourceFile = node.getSourceFile(); + compilationUnit = node; + return super.visitCompilationUnit(node, unused); + } + + public void replaceMethodName(JCTree.JCMethodInvocation node) { + var select = node.getMethodSelect(); + if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return; + if (!fieldAccess.name.contentEquals("methodName")) return; + if (!(node.args.head instanceof JCTree.JCMemberReference methodReference)) { + plugin.utils.reportError(sourceFile, node, "Please provide a Class::method reference directly (and nothing else)"); + return; + } + var clearName = methodReference.name.toString(); + var classRef = methodReference.expr; + var type = plugin.utils.resolveClassName(classRef, compilationUnit); + var intermediaryName = mappings.resolveMethodToIntermediary( + type.tsym.flatName().toString(), + clearName + ); + fieldAccess.name = plugin.names.fromString("id"); + node.args = List.of(plugin.treeMaker.Literal(intermediaryName)); + } + + public void replaceClassName(JCTree.JCMethodInvocation node) { + var select = node.getMethodSelect(); + if (!(select instanceof JCTree.JCFieldAccess fieldAccess)) return; + if (!fieldAccess.name.contentEquals("className")) return; + if (node.getTypeArguments().size() != 1) { + plugin.utils.reportError(sourceFile, node, "You need to explicitly provide the class you want the intermediary name for"); + return; + } + var head = node.typeargs.head; + var resolved = plugin.utils.resolveClassName(head, compilationUnit); + var mappedName = mappings.resolveClassToIntermediary(resolved.tsym.flatName().toString()); + fieldAccess.name = plugin.names.fromString("id"); + node.typeargs = List.nil(); + node.args = List.of(plugin.treeMaker.Literal(mappedName)); + } + + @Override + public Void visitMethodInvocation(MethodInvocationTree node, Void unused) { + replaceClassName((JCTree.JCMethodInvocation) node); + replaceMethodName((JCTree.JCMethodInvocation) node); + return super.visitMethodInvocation(node, unused); + } +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionPlugin.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionPlugin.java new file mode 100644 index 0000000..ba6a0c5 --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionPlugin.java @@ -0,0 +1,25 @@ +package moe.nea.firmament.javaplugin; + +import com.sun.source.util.JavacTask; +import com.sun.source.util.Plugin; + +import java.util.HashMap; +import java.util.Map; + +public class IntermediaryNameResolutionPlugin implements Plugin { + + @Override + public String getName() { + return "IntermediaryNameReplacement"; + } + + @Override + public void init(JavacTask task, String... args) { + Map<String, String> argMap = new HashMap<>(); + for (String arg : args) { + String[] parts = arg.split("=", 2); + argMap.put(parts[0], parts.length == 2 ? parts[1] : "true"); + } + task.addTaskListener(new IntermediaryNameResolutionTask(this, task, argMap)); + } +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionTask.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionTask.java new file mode 100644 index 0000000..86a5598 --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/IntermediaryNameResolutionTask.java @@ -0,0 +1,44 @@ +package moe.nea.firmament.javaplugin; + +import com.sun.source.util.JavacTask; +import com.sun.source.util.TaskEvent; +import com.sun.source.util.TaskListener; +import com.sun.tools.javac.api.BasicJavacTask; +import com.sun.tools.javac.tree.TreeMaker; +import com.sun.tools.javac.util.Names; +import net.fabricmc.stitch.commands.tinyv2.TinyV2Reader; + +import java.io.File; +import java.io.IOException; +import java.util.Map; + +public class IntermediaryNameResolutionTask implements TaskListener { + TreeMaker treeMaker; + Names names; + MappingTree mappings; + Utils utils; + + public IntermediaryNameResolutionTask(IntermediaryNameResolutionPlugin intermediaryNameResolutionPlugin, JavacTask task, Map<String, String> argMap) { + var context = ((BasicJavacTask) task).getContext(); + var mappingFile = new File(argMap.get("mappingFile")); + System.err.println("Loading mappings from " + mappingFile); + try { + var tinyV2File = TinyV2Reader.read(mappingFile.toPath()); + mappings = new MappingTree(tinyV2File, argMap.get("sourceNs"), argMap.getOrDefault("targetNs", "intermediary")); + } catch (IOException e) { + throw new RuntimeException(e); + } + treeMaker = TreeMaker.instance(context); + names = Names.instance(context); + utils = Utils.instance(context); + } + + @Override + public void finished(TaskEvent e) { + if (e.getKind() != TaskEvent.Kind.ENTER) return; + if (e.getCompilationUnit() == null || e.getSourceFile() == null) return; + e.getCompilationUnit().accept(new InitReplacer(mappings, this), null); + e.getCompilationUnit().accept(new IntermediaryMethodReplacer(mappings, this), null); + } + +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/MappingTree.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/MappingTree.java new file mode 100644 index 0000000..7a270b7 --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/MappingTree.java @@ -0,0 +1,48 @@ +package moe.nea.firmament.javaplugin; + +import net.fabricmc.stitch.commands.tinyv2.TinyClass; +import net.fabricmc.stitch.commands.tinyv2.TinyFile; +import net.fabricmc.stitch.commands.tinyv2.TinyMethod; + +import java.util.Map; +import java.util.stream.Collectors; + +public class MappingTree { + + private final Map<String, TinyClass> classLookup; + private final int targetIndex; + private final int sourceIndex; + + public MappingTree(TinyFile tinyV2File, String sourceNamespace, String targetNamespace) { + sourceIndex = tinyV2File.getHeader().getNamespaces().indexOf(sourceNamespace); + if (sourceIndex < 0) + throw new RuntimeException("Could not find source namespace " + sourceNamespace + " in mappings file."); + this.classLookup = tinyV2File + .getClassEntries() + .stream() + .collect(Collectors.toMap(it -> it.getClassNames().get(sourceIndex), it -> it)); + targetIndex = tinyV2File.getHeader().getNamespaces().indexOf(targetNamespace); + if (targetIndex < 0) + throw new RuntimeException("Could not find target namespace " + targetNamespace + " in mappings file."); + } + + public String resolveMethodToIntermediary(String className, String methodName) { + var classData = classLookup.get(className.replace(".", "/")); + TinyMethod candidate = null; + for (TinyMethod method : classData.getMethods()) { + if (method.getMethodNames().get(sourceIndex).equals(methodName)) { + if (candidate != null) { + throw new RuntimeException("Found two candidates for method " + className + "." + methodName); + } + candidate = method; + } + } + return candidate.getMethodNames().get(targetIndex); + } + + public String resolveClassToIntermediary(String className) { + return classLookup.get(className.replace(".", "/")) + .getClassNames().get(targetIndex) + .replace("/", "."); + } +} diff --git a/javaplugin/src/main/java/moe/nea/firmament/javaplugin/Utils.java b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/Utils.java new file mode 100644 index 0000000..d9008bd --- /dev/null +++ b/javaplugin/src/main/java/moe/nea/firmament/javaplugin/Utils.java @@ -0,0 +1,121 @@ +package moe.nea.firmament.javaplugin; + +import com.sun.source.tree.AnnotationTree; +import com.sun.source.tree.AssignmentTree; +import com.sun.source.tree.ClassTree; +import com.sun.source.tree.CompilationUnitTree; +import com.sun.source.tree.ExpressionTree; +import com.sun.source.tree.IdentifierTree; +import com.sun.source.tree.MemberSelectTree; +import com.sun.source.tree.Tree; +import com.sun.tools.javac.comp.Attr; +import com.sun.tools.javac.code.Symbol; +import com.sun.tools.javac.code.Type; +import com.sun.tools.javac.code.Types; +import com.sun.tools.javac.comp.AttrContext; +import com.sun.tools.javac.comp.Enter; +import com.sun.tools.javac.comp.Env; +import com.sun.tools.javac.tree.JCTree; +import com.sun.tools.javac.util.Context; +import com.sun.tools.javac.util.JCDiagnostic; +import com.sun.tools.javac.util.JavacMessages; +import com.sun.tools.javac.util.Log; + +import javax.tools.JavaFileObject; +import java.util.ListResourceBundle; + +public class Utils { + private static final Context.Key<Utils> KEY = new Context.Key<>(); + private final Log log; + private final JCDiagnostic.Factory diagnostics; + private final Types types; + private final Attr attr; + private final Enter enter; + + private Utils(Context context) { + context.put(KEY, this); + JavacMessages.instance(context).add(l -> new ListResourceBundle() { + + @Override + protected Object[][] getContents() { + return new Object[][]{ + new Object[]{"compiler.err.firmament.generic", "{0}"} + }; + } + }); + log = Log.instance(context); + diagnostics = JCDiagnostic.Factory.instance(context); + types = Types.instance(context); + attr = Attr.instance(context); + enter = Enter.instance(context); + } + + public static Utils instance(Context context) { + var utils = context.get(KEY); + if (utils == null) { + utils = new Utils(context); + } + return utils; + } + + public Type resolveClassName(ExpressionTree expression) { + var tree = (JCTree) expression; + return tree.type; + } + + public Type resolveClassName(ExpressionTree tree, CompilationUnitTree unit) { + return resolveClassName(tree, enter.getTopLevelEnv((JCTree.JCCompilationUnit) unit)); + } + + public Type resolveClassName(ExpressionTree tree, Env<AttrContext> env) { + var t = resolveClassName(tree); + if (t != null) return t; + return attr.attribType((JCTree) tree, env); + } + + public Symbol getSymbol(IdentifierTree tree) { + return ((JCTree.JCIdent) tree).sym; + } + + public Symbol.ClassSymbol getSymbol(ClassTree tree) { + return ((JCTree.JCClassDecl) tree).sym; + } + + public ExpressionTree getAnnotationValue( + AnnotationTree tree, + String name) { + // TODO: strip parenthesis + for (var argument : tree.getArguments()) { + var assignment = (AssignmentTree) argument; + if (((IdentifierTree) assignment.getVariable()).getName().toString().equals(name)) + return assignment.getExpression(); + } + return null; + } + + public Type.ClassType resolveClassLiteralExpression(ExpressionTree tree) { + if (!(tree instanceof MemberSelectTree select)) + throw new RuntimeException("Cannot resolve non field access class literal: " + tree); + if (!select.getIdentifier().toString().equals("class")) + throw new RuntimeException("Class literal " + select + "accessed non .class attribute"); + + return (Type.ClassType) resolveClassName(select.getExpression()); + } + + public void reportError( + JavaFileObject file, + Tree node, + String message + ) { + var originalSource = log.useSource(file); + var error = diagnostics.error( + JCDiagnostic.DiagnosticFlag.API, + log.currentSource(), + ((JCTree) node).pos(), + "firmament.generic", + message + ); + log.report(error); + log.useSource(originalSource); + } +} diff --git a/javaplugin/src/main/resources/META-INF/services/com.sun.source.util.Plugin b/javaplugin/src/main/resources/META-INF/services/com.sun.source.util.Plugin new file mode 100644 index 0000000..a9e5dbe --- /dev/null +++ b/javaplugin/src/main/resources/META-INF/services/com.sun.source.util.Plugin @@ -0,0 +1 @@ +moe.nea.firmament.javaplugin.IntermediaryNameResolutionPlugin diff --git a/settings.gradle.kts b/settings.gradle.kts index 79cf1a4..36e7e3e 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -33,3 +33,4 @@ pluginManagement { rootProject.name = "Firmament" include("symbols") +include("javaplugin") diff --git a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java index 18dfa17..0a5bedd 100644 --- a/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java +++ b/src/main/java/moe/nea/firmament/init/ClientPlayerRiser.java @@ -1,4 +1,3 @@ - package moe.nea.firmament.init; import me.shedaniel.mm.api.ClassTinkerers; @@ -14,11 +13,15 @@ import java.lang.reflect.Modifier; import java.util.Objects; public class ClientPlayerRiser extends RiserUtils { - String PlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_1657"); - String World = remapper.mapClassName("intermediary", "net.minecraft.class_1937"); + @IntermediaryName(net.minecraft.entity.player.PlayerEntity.class) + String PlayerEntity; + @IntermediaryName(net.minecraft.world.World.class) + String World; String GameProfile = "com.mojang.authlib.GameProfile"; - String BlockPos = remapper.mapClassName("intermediary", "net.minecraft.class_2338"); - String AbstractClientPlayerEntity = remapper.mapClassName("intermediary", "net.minecraft.class_742"); + @IntermediaryName(net.minecraft.util.math.BlockPos.class) + String BlockPos; + @IntermediaryName(net.minecraft.client.network.AbstractClientPlayerEntity.class) + String AbstractClientPlayerEntity; String GuiPlayer = "moe.nea.firmament.gui.entity.GuiPlayer"; // World world, BlockPos pos, float yaw, GameProfile gameProfile Type constructorDescriptor = Type.getMethodType(Type.VOID_TYPE, getTypeForClassName(World), getTypeForClassName(BlockPos), Type.FLOAT_TYPE, getTypeForClassName(GameProfile)); diff --git a/src/main/java/moe/nea/firmament/init/EarlyRiser.java b/src/main/java/moe/nea/firmament/init/EarlyRiser.java index 77c044d..5eab563 100644 --- a/src/main/java/moe/nea/firmament/init/EarlyRiser.java +++ b/src/main/java/moe/nea/firmament/init/EarlyRiser.java @@ -6,5 +6,6 @@ public class EarlyRiser implements Runnable { public void run() { new ClientPlayerRiser().addTinkerers(); new HandledScreenRiser().addTinkerers(); + new SectionBuilderRiser().addTinkerers(); } } diff --git a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java index 0215523..baa0501 100644 --- a/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java +++ b/src/main/java/moe/nea/firmament/init/HandledScreenRiser.java @@ -2,6 +2,8 @@ package moe.nea.firmament.init; import me.shedaniel.mm.api.ClassTinkerers; +import net.minecraft.client.gui.screen.Screen; +import net.minecraft.client.gui.screen.ingame.HandledScreen; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.ClassNode; @@ -17,8 +19,10 @@ import org.objectweb.asm.tree.VarInsnNode; import java.lang.reflect.Modifier; public class HandledScreenRiser extends RiserUtils { - String Screen = remapper.mapClassName("intermediary", "net.minecraft.class_437"); - String HandledScreen = remapper.mapClassName("intermediary", "net.minecraft.class_465"); + @IntermediaryName(net.minecraft.client.gui.screen.Screen.class) + String Screen; + @IntermediaryName(net.minecraft.client.gui.screen.ingame.HandledScreen.class) + String HandledScreen; Type mouseScrolledDesc = Type.getMethodType(Type.BOOLEAN_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE, Type.DOUBLE_TYPE); String mouseScrolled = remapper.mapMethodName("intermediary", "net.minecraft.class_364", "method_25401", mouseScrolledDesc.getDescriptor()); diff --git a/src/main/java/moe/nea/firmament/init/Intermediary.java b/src/main/java/moe/nea/firmament/init/Intermediary.java new file mode 100644 index 0000000..61494d7 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/Intermediary.java @@ -0,0 +1,63 @@ +package moe.nea.firmament.init; + +import net.fabricmc.loader.api.FabricLoader; +import net.fabricmc.loader.api.MappingResolver; +import org.objectweb.asm.Type; + +import java.util.List; + +public class Intermediary { + private static final MappingResolver RESOLVER = FabricLoader.getInstance().getMappingResolver(); + + static String methodName(Object object) { + throw new AssertionError("Cannot be called at runtime"); + } + + static <T> String className() { + throw new AssertionError("Cannot be called at runtime"); + } + + static String id(String source) { + return source; + } + +// public record Class( +// Type intermediaryClass +// ) { +// public Class(String intermediaryClass) { +// this(Type.getObjectType(intermediaryClass.replace('.', '/'))); +// } +// +// public String getMappedName() { +// return RESOLVER.mapClassName("intermediary", intermediaryClass.getInternalName() +// .replace('/', '.')); +// } +// } +// +// public record Method( +// Type intermediaryClassName, +// String intermediaryMethodName, +// Type intermediaryReturnType, +// List<Type> intermediaryArgumentTypes +// ) { +// public Method( +// String intermediaryClassName, +// String intermediaryMethodName, +// String intermediaryReturnType, +// String... intermediaryArgumentTypes +// ) { +// this(intermediaryClassName, intermediaryMethodName, intermediaryReturnType, List.of(intermediaryArgumentTypes)); +// } +// +// public String getMappedMethodName() { +// return RESOLVER.mapMethodName("intermediary", +// intermediaryClassName.getInternalName().replace('/', '.')); +// } +// +// public Type getIntermediaryDescriptor() { +// return Type.getMethodType(intermediaryReturnType, intermediaryArgumentTypes.toArray(Type[]::new)); +// } +// +// +// } +} diff --git a/src/main/java/moe/nea/firmament/init/IntermediaryName.java b/src/main/java/moe/nea/firmament/init/IntermediaryName.java new file mode 100644 index 0000000..a22ad0f --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/IntermediaryName.java @@ -0,0 +1,21 @@ +package moe.nea.firmament.init; + +import net.fabricmc.loader.api.MappingResolver; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Injects the intermediary name of the given field into this field by replacing its initializer with a call to + * {@link MappingResolver#mapClassName(String, String)} + */ +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.FIELD) +public @interface IntermediaryName { + // String method() default ""; +// +// String field() default ""; + Class<?> value(); +} diff --git a/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java new file mode 100644 index 0000000..2be11a6 --- /dev/null +++ b/src/main/java/moe/nea/firmament/init/SectionBuilderRiser.java @@ -0,0 +1,116 @@ +package moe.nea.firmament.init; + +import me.shedaniel.mm.api.ClassTinkerers; +import net.fabricmc.loader.api.FabricLoader; +import net.minecraft.block.BlockState; +import net.minecraft.client.render.block.BlockModels; +import net.minecraft.client.render.block.BlockRenderManager; +import net.minecraft.client.render.chunk.SectionBuilder; +import net.minecraft.client.render.model.BakedModel; +import net.minecraft.util.math.BlockPos; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.VarInsnNode; + +public class SectionBuilderRiser extends RiserUtils { + + @IntermediaryName(SectionBuilder.class) + String SectionBuilder; + @IntermediaryName(BlockPos.class) + String BlockPos; + @IntermediaryName(BlockRenderManager.class) + String BlockRenderManager; + @IntermediaryName(BlockState.class) + String BlockState; + @IntermediaryName(BakedModel.class) + String BakedModel; + String CustomBlockTextures = "moe.nea.firmament.features.texturepack.CustomBlockTextures"; + + Type getModelDesc = Type.getMethodType( + getTypeForClassName(BlockRenderManager), + getTypeForClassName(BlockState) + ); + String getModel = remapper.mapMethodName( + "intermediary", + Intermediary.<BlockRenderManager>className(), + Intermediary.methodName(net.minecraft.client.render.block.BlockRenderManager::getModel), + Type.getMethodDescriptor( + getTypeForClassName(Intermediary.<BakedModel>className()), + getTypeForClassName(Intermediary.<BlockState>className()) + ) + ); + + @Override + public void addTinkerers() { + if (FabricLoader.getInstance().isModLoaded("fabric-renderer-indigo")) + ClassTinkerers.addTransformation(SectionBuilder, this::handle, true); + } + + private void handle(ClassNode classNode) { + for (MethodNode method : classNode.methods) { + if (method.name.endsWith("$fabric-renderer-indigo$hookChunkBuildTessellate") && + method.name.startsWith("redirect$")) { + handleIndigo(method); + return; + } + } + new RuntimeException("Could not inject tesselation hook despite fabric renderer indigo being loaded").printStackTrace(); + } + + private void handleIndigo(MethodNode method) { + LocalVariableNode blockPosVar = null, blockStateVar = null; + for (LocalVariableNode localVariable : method.localVariables) { + if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockPos))) { + blockPosVar = localVariable; + } + if (Type.getType(localVariable.desc).equals(getTypeForClassName(BlockState))) { + blockStateVar = localVariable; + } + } + if (blockPosVar == null || blockStateVar == null) { + System.err.println("Firmament could inject into indigo: missing either block pos or blockstate"); + return; + } + for (AbstractInsnNode instruction : method.instructions) { + if (instruction.getOpcode() != Opcodes.INVOKEVIRTUAL) continue; + var methodInsn = (MethodInsnNode) instruction; + if (!(methodInsn.name.equals(getModel) && Type.getObjectType(methodInsn.owner).equals(getTypeForClassName(BlockRenderManager)))) + continue; + method.instructions.insertBefore( + methodInsn, + new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "enterFallbackCall", + Type.getMethodDescriptor(Type.VOID_TYPE) + )); + + var insnList = new InsnList(); + insnList.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "exitFallbackCall", + Type.getMethodDescriptor(Type.VOID_TYPE) + )); + insnList.add(new VarInsnNode(Opcodes.ALOAD, blockPosVar.index)); + insnList.add(new VarInsnNode(Opcodes.ALOAD, blockStateVar.index)); + insnList.add(new MethodInsnNode( + Opcodes.INVOKESTATIC, + getTypeForClassName(CustomBlockTextures).getInternalName(), + "patchIndigo", + Type.getMethodDescriptor(getTypeForClassName(BakedModel), + getTypeForClassName(BakedModel), + getTypeForClassName(BlockPos), + getTypeForClassName(BlockState)), + false + )); + method.instructions.insert(methodInsn, insnList); + } + } +} diff --git a/src/main/kotlin/moe/nea/firmament/Firmament.kt b/src/main/kotlin/moe/nea/firmament/Firmament.kt index 4b742e8..400dcf2 100644 --- a/src/main/kotlin/moe/nea/firmament/Firmament.kt +++ b/src/main/kotlin/moe/nea/firmament/Firmament.kt @@ -33,6 +33,7 @@ import kotlinx.coroutines.plus import kotlinx.serialization.json.Json import kotlinx.serialization.json.decodeFromStream import kotlin.coroutines.EmptyCoroutineContext +import net.minecraft.client.render.chunk.SectionBuilder import net.minecraft.command.CommandRegistryAccess import net.minecraft.util.Identifier import moe.nea.firmament.commands.registerFirmamentCommand @@ -112,6 +113,8 @@ object Firmament { ClientTickEvents.END_CLIENT_TICK.register(ClientTickEvents.EndTick { instance -> TickEvent.publish(TickEvent(tick++)) }) + // TODO: remove me + Class.forName(SectionBuilder::class.java.name) IDataHolder.registerEvents() RepoManager.initialize() SBData.init() diff --git a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt index 2289be2..c869ba4 100644 --- a/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt +++ b/src/main/kotlin/moe/nea/firmament/features/texturepack/CustomBlockTextures.kt @@ -157,7 +157,10 @@ object CustomBlockTextures { currentIslandReplacements = replacements if (lastReplacements != replacements) { MC.nextTick { - MC.worldRenderer.reload() + MC.worldRenderer.chunks?.chunks?.forEach { + // false schedules rebuilds outside a 27 block radius to happen async + it.scheduleRebuild(false) + } } } } @@ -259,6 +262,10 @@ object CustomBlockTextures { return BakedReplacements(map.mapValues { LocationReplacements(it.value) }) } + @JvmStatic + fun patchIndigo(orig: BakedModel, pos: BlockPos, state: BlockState): BakedModel { + return getReplacementModel(state, pos) ?: orig + } @Subscribe fun onStart(event: FinalizeResourceManagerEvent) { diff --git a/src/main/resources/firmament.accesswidener b/src/main/resources/firmament.accesswidener index b69b99d..c69725f 100644 --- a/src/main/resources/firmament.accesswidener +++ b/src/main/resources/firmament.accesswidener @@ -20,4 +20,4 @@ mutable field net/minecraft/screen/slot/Slot x I mutable field net/minecraft/screen/slot/Slot y I accessible field net/minecraft/entity/player/PlayerEntity PLAYER_MODEL_PARTS Lnet/minecraft/entity/data/TrackedData; - +accessible field net/minecraft/client/render/WorldRenderer chunks Lnet/minecraft/client/render/BuiltChunkStorage; |