version = getSubprojectVersion(project) loom { accessWidenerPath = file('src/main/resources/fabric-transitive-access-wideners-v1.accesswidener') } testDependencies(project, [ ':fabric-rendering-v1', ':fabric-object-builder-api-v1' ]) import org.objectweb.asm.ClassReader import org.objectweb.asm.Opcodes import org.objectweb.asm.Type import org.objectweb.asm.tree.ClassNode import java.nio.file.FileSystem import java.nio.file.FileSystems import java.nio.file.Files import java.nio.file.Path import java.util.stream.Collectors task generateAccessWidener { doLast { List lines = new ArrayList<>(); lines.add("accessWidener v2 named") lines.add("") lines.add("# DO NOT EDIT BY HAND! This file is generated automatically.") lines.add("# Edit \"template.accesswidener\" instead then run \"gradlew generateAccessWidener\".") lines.add("") lines.addAll(file("template.accesswidener").text.lines().toList()) Path commonJar = loom.namedMinecraftProvider.parentMinecraftProvider.commonJar.path FileSystems.newFileSystem(URI.create("jar:${commonJar.toUri()}"), [create: false]).withCloseable { fs -> generateBlockConstructors(lines, fs) lines.add("") generateItemConstructors(lines, fs) lines.add("") } Path clientJar = loom.namedMinecraftProvider.parentMinecraftProvider.clientOnlyJar.path FileSystems.newFileSystem(URI.create("jar:${clientJar.toUri()}"), [create: false]).withCloseable { fs -> generateRenderPhaseFields(lines, fs) lines.add("") generateRenderPhaseInnerClasses(lines, fs) } file('src/main/resources/fabric-transitive-access-wideners-v1.accesswidener').text = String.join('\n', lines) + '\n' validateAccessWidener(lines) } } def generateBlockConstructors(List lines, FileSystem fs) { lines.add("# Constructors of non-abstract block classes") Files.list(fs.getPath("net/minecraft/block")) .filter { Files.isRegularFile(it) && it.toString().endsWith(".class") } .map { loadClass(it) } .sorted(Comparator.comparing { it.name }) .filter { (it.access & Opcodes.ACC_ABSTRACT) == 0 } .forEach { node -> for (def method : node.methods) { // Checklist for finding block constructors as of 1.19.3: // - class directly in net.minecraft.block (excluding subpackages) // - method name == (by definition) // - contains an AbstractBlock$Settings parameter // - only taking into account non-abstract classes and non-public constructors // Block constructor... if (method.name == "" && Type.getArgumentTypes(method.desc).any { it.internalName == 'net/minecraft/block/AbstractBlock$Settings' }) { // ...and non-public if ((method.access & Opcodes.ACC_PUBLIC) == 0) { lines.add("transitive-accessible method $node.name $method.desc") } } } } } def generateItemConstructors(List lines, FileSystem fs) { lines.add("# Constructors of non-abstract item classes") Files.list(fs.getPath("net/minecraft/item")) .filter { Files.isRegularFile(it) && it.toString().endsWith(".class") } .map { loadClass(it) } .sorted(Comparator.comparing { it.name }) .filter { (it.access & Opcodes.ACC_ABSTRACT) == 0 } .forEach { node -> for (def method : node.methods) { // Checklist for finding item constructors as of 1.19.3: // - class directly in net.minecraft.item (excluding subpackages) // - method name == (by definition) // - contains an Item$Settings parameter // - only taking into account non-abstract classes and non-public constructors // Item constructor... if (method.name == "" && Type.getArgumentTypes(method.desc).any { it.internalName == 'net/minecraft/item/Item$Settings' }) { // ...and non-public if ((method.access & Opcodes.ACC_PUBLIC) == 0) { lines.add("transitive-accessible method $node.name $method.desc") } } } } } def generateRenderPhaseFields(List lines, FileSystem fs) { lines.add("# Protected static fields of RenderPhase") for (def field : loadClass(fs.getPath("net/minecraft/client/render/RenderPhase.class")).fields) { // All protected static fields of RenderPhase if ((field.access & Opcodes.ACC_PROTECTED) != 0 && (field.access & Opcodes.ACC_STATIC) != 0) { lines.add("transitive-accessible field net/minecraft/client/render/RenderPhase ${field.name} ${field.desc}") } } } def generateRenderPhaseInnerClasses(List lines, FileSystem fs) { lines.add("# Protected static inner classes of RenderPhase") for (def innerClass : loadClass(fs.getPath("net/minecraft/client/render/RenderPhase.class")).innerClasses) { // All protected static inner classes of RenderPhase if ((innerClass.access & Opcodes.ACC_PROTECTED) != 0 && (innerClass.access & Opcodes.ACC_STATIC) != 0) { lines.add("transitive-accessible class net/minecraft/client/render/RenderPhase\$${innerClass.innerName}") } } } ClassNode loadClass(Path path) { def node = new ClassNode() Files.newInputStream(path).withCloseable { is -> new ClassReader(is).accept(node, ClassReader.SKIP_CODE | ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES) } return node } def validateAccessWidener(List lines) { List exceptions = new ArrayList<>() for (int i = 0; i < lines.size(); i++) { String line = lines.get(i) if (line.isBlank() || line.startsWith("#") || line.startsWith("transitive-") || line.startsWith("accessWidener")) continue exceptions.add(String.valueOf(i + 1)) } if (exceptions.size() > 0) { throw new InvalidUserDataException("\"fabric-transitive-access-wideners-v1.accesswidener\" contains non-transitive access modifiers at lines: [" + String.join(", ", exceptions) + "]") } } generateResources.dependsOn generateAccessWidener