Skip to content

Commit

Permalink
Preliminary Support for NeoForge (should re-evaluate if plugin & tran…
Browse files Browse the repository at this point in the history
…sformer is needed in the future)
  • Loading branch information
shedaniel committed Nov 14, 2023
1 parent 64f4523 commit 0a64248
Show file tree
Hide file tree
Showing 6 changed files with 297 additions and 94 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/*
* This file is licensed under the MIT License, part of architectury-transformer.
* Copyright (c) 2020, 2021, 2022 architectury
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.architectury.transformer.transformers;

import dev.architectury.transformer.input.FileAccess;
import dev.architectury.transformer.transformers.base.edit.TransformerContext;
import org.objectweb.asm.AnnotationVisitor;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;

/**
* Generates a fake neoforge mod.
*/
public class GenerateFakeNeoForgeMod extends AbstractFakeMod {
@Override
public void doEdit(TransformerContext context, FileAccess output) throws Exception {
String fakeModId = generateModId();
output.addFile("META-INF/mods.toml",
"modLoader = \"javafml\"\n" +
"loaderVersion = \"[1,)\"\n" +
"license = \"Generated\"\n" +
"[[mods]]\n" +
"modId = \"" + fakeModId + "\"\n");
output.addFile("pack.mcmeta",
"{\"pack\":{\"description\":\"Generated\",\"pack_format\":" + System.getProperty(BuiltinProperties.MCMETA_VERSION, "4") + "}}");
output.addFile("generated" + fakeModId + "/" + fakeModId + ".class", generateClass(fakeModId));
}

private byte[] generateClass(String fakeModId) {
ClassWriter writer = new ClassWriter(0);
writer.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC, "generated" + fakeModId + "/" + fakeModId, null, "java/lang/Object", null);
AnnotationVisitor modAnnotation = writer.visitAnnotation("Lnet/neoforged/fml/common/Mod;", false);
modAnnotation.visit("value", fakeModId);
modAnnotation.visitEnd();
{
MethodVisitor method = writer.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, new String[0]);
method.visitVarInsn(Opcodes.ALOAD, 0);
method.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
method.visitInsn(Opcodes.RETURN);
method.visitMaxs(1, 1);
method.visitEnd();
}
writer.visitEnd();
return writer.toByteArray();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,7 @@ private static String getPlatformClass(String lookupClass) {
String platform = System.getProperty(BuiltinProperties.PLATFORM_NAME);
Preconditions.checkNotNull(platform, BuiltinProperties.PLATFORM_NAME + " is not present!");
if (platform.equals("quilt")) platform = "fabric";
if (platform.equals("neoforge")) platform = "forge";

This comment has been minimized.

Copy link
@Juuxel

Juuxel Nov 14, 2023

Member

:ohno:

This comment has been minimized.

Copy link
@shedaniel

shedaniel Nov 14, 2023

Author Member

I honestly don't know if this should be how it is, maybe I will add a config option

String lookupType = lookupClass.replace("$", "") + "Impl";

return lookupType.substring(0, lookupType.lastIndexOf('/')) + "/" + platform + "/" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,110 +23,19 @@

package dev.architectury.transformer.transformers;

import dev.architectury.transformer.transformers.base.ClassEditTransformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* Handle @ForgeEvent and @ForgeEventCancellable and promote @Environment from being an invisible annotation to being an visible annotation.
*/
public class TransformForgeAnnotations implements ClassEditTransformer {
public class TransformForgeAnnotations extends TransformForgeLikeAnnotations {
public static final String FORGE_EVENT_LEGACY = "Lme/shedaniel/architectury/ForgeEvent;";
public static final String FORGE_EVENT = "Ldev/architectury/annotations/ForgeEvent;";
public static final String FORGE_EVENT_CANCELLABLE_LEGACY = "Lme/shedaniel/architectury/ForgeEventCancellable;";
public static final String FORGE_EVENT_CANCELLABLE = "Ldev/architectury/annotations/ForgeEventCancellable;";
public static final String CANCELABLE = "Lnet/minecraftforge/eventbus/api/Cancelable;";

private static final String ENVIRONMENT = "net/fabricmc/api/Environment";
private static final String ONLY_IN = "net/minecraftforge/api/distmarker/OnlyIn";

@Override
public ClassNode doEdit(String name, ClassNode node) {
if ((node.access & Opcodes.ACC_INTERFACE) == 0) {
if (node.visibleAnnotations != null && node.visibleAnnotations.stream().anyMatch(
annotation -> Objects.equals(annotation.desc, FORGE_EVENT) || Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE)
|| Objects.equals(annotation.desc, FORGE_EVENT_LEGACY) || Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE_LEGACY)
)) {
node.superName = "net/minecraftforge/eventbus/api/Event";
for (MethodNode method : node.methods) {
if (Objects.equals(method.name, "<init>")) {
for (AbstractInsnNode insnNode : method.instructions) {
if (insnNode.getOpcode() == Opcodes.INVOKESPECIAL) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (Objects.equals(methodInsnNode.name, "<init>") && Objects.equals(methodInsnNode.owner, "java/lang/Object")) {
methodInsnNode.owner = "net/minecraftforge/eventbus/api/Event";
break;
}
}
}
}
}
if (node.signature != null) {
int index = node.signature.lastIndexOf('L');
String s = index == -1 ? node.signature : node.signature.substring(0, index);
node.signature = s + "Lnet/minecraftforge/eventbus/api/Event;";
}
// if @ForgeEventCancellable, add the cancellable annotation from forge
if ((node.visibleAnnotations.stream().anyMatch(annotation -> Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE_LEGACY))
|| node.visibleAnnotations.stream().anyMatch(annotation -> Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE))) &&
node.visibleAnnotations.stream().noneMatch(annotation -> Objects.equals(annotation.desc, CANCELABLE))) {
node.visibleAnnotations.add(new AnnotationNode(CANCELABLE));
}
}
}
if (node.visibleAnnotations == null) {
node.visibleAnnotations = new ArrayList<>();
}
{
Collection<AnnotationNode> invisibleEnvironments;
if (node.invisibleAnnotations != null) {
invisibleEnvironments = node.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + ONLY_IN + ";"))
.collect(Collectors.toList());
node.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
node.visibleAnnotations.addAll(invisibleEnvironments);
}
for (FieldNode field : node.fields) {
if (field.visibleAnnotations == null) {
field.visibleAnnotations = new ArrayList<>();
}

Collection<AnnotationNode> invisibleEnvironments;
if (field.invisibleAnnotations != null) {
invisibleEnvironments = field.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + ONLY_IN + ";"))
.collect(Collectors.toList());
field.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
field.visibleAnnotations.addAll(invisibleEnvironments);
}
for (MethodNode method : node.methods) {
if (method.visibleAnnotations == null) {
method.visibleAnnotations = new ArrayList<>();
}

Collection<AnnotationNode> invisibleEnvironments;
if (method.invisibleAnnotations != null) {
invisibleEnvironments = method.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + ONLY_IN + ";"))
.collect(Collectors.toList());
method.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
method.visibleAnnotations.addAll(invisibleEnvironments);
}
return node;
public TransformForgeAnnotations() {
super(ONLY_IN);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
/*
* This file is licensed under the MIT License, part of architectury-transformer.
* Copyright (c) 2020, 2021, 2022 architectury
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.architectury.transformer.transformers;

import dev.architectury.transformer.transformers.base.ClassEditTransformer;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.tree.*;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Objects;
import java.util.stream.Collectors;

/**
* Handle @ForgeEvent and @ForgeEventCancellable and promote @Environment from being an invisible annotation to being an visible annotation.
*/
public class TransformForgeLikeAnnotations implements ClassEditTransformer {
public static final String FORGE_EVENT_LEGACY = "Lme/shedaniel/architectury/ForgeEvent;";
public static final String FORGE_EVENT = "Ldev/architectury/annotations/ForgeEvent;";
public static final String FORGE_EVENT_CANCELLABLE_LEGACY = "Lme/shedaniel/architectury/ForgeEventCancellable;";
public static final String FORGE_EVENT_CANCELLABLE = "Ldev/architectury/annotations/ForgeEventCancellable;";
public static final String CANCELABLE = "Lnet/minecraftforge/eventbus/api/Cancelable;";

private static final String ENVIRONMENT = "net/fabricmc/api/Environment";
private static final String FORGE_ONLY_IN = "net/minecraftforge/api/distmarker/OnlyIn";
protected static final String NEOFORGE_ONLY_IN = "net/neoforged/api/distmarker/OnlyIn";

private final String onlyIn;

public TransformForgeLikeAnnotations(String onlyIn) {
this.onlyIn = onlyIn;
}

@Override
public ClassNode doEdit(String name, ClassNode node) {
if ((node.access & Opcodes.ACC_INTERFACE) == 0) {
if (node.visibleAnnotations != null && node.visibleAnnotations.stream().anyMatch(
annotation -> Objects.equals(annotation.desc, FORGE_EVENT) || Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE)
|| Objects.equals(annotation.desc, FORGE_EVENT_LEGACY) || Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE_LEGACY)
)) {
node.superName = "net/minecraftforge/eventbus/api/Event";
for (MethodNode method : node.methods) {
if (Objects.equals(method.name, "<init>")) {
for (AbstractInsnNode insnNode : method.instructions) {
if (insnNode.getOpcode() == Opcodes.INVOKESPECIAL) {
MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode;
if (Objects.equals(methodInsnNode.name, "<init>") && Objects.equals(methodInsnNode.owner, "java/lang/Object")) {
methodInsnNode.owner = "net/minecraftforge/eventbus/api/Event";
break;
}
}
}
}
}
if (node.signature != null) {
int index = node.signature.lastIndexOf('L');
String s = index == -1 ? node.signature : node.signature.substring(0, index);
node.signature = s + "Lnet/minecraftforge/eventbus/api/Event;";
}
// if @ForgeEventCancellable, add the cancellable annotation from forge
if ((node.visibleAnnotations.stream().anyMatch(annotation -> Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE_LEGACY))
|| node.visibleAnnotations.stream().anyMatch(annotation -> Objects.equals(annotation.desc, FORGE_EVENT_CANCELLABLE))) &&
node.visibleAnnotations.stream().noneMatch(annotation -> Objects.equals(annotation.desc, CANCELABLE))) {
node.visibleAnnotations.add(new AnnotationNode(CANCELABLE));
}
}
}
if (node.visibleAnnotations == null) {
node.visibleAnnotations = new ArrayList<>();
}
{
Collection<AnnotationNode> invisibleEnvironments;
if (node.invisibleAnnotations != null) {
invisibleEnvironments = node.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + onlyIn + ";"))
.collect(Collectors.toList());
node.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
node.visibleAnnotations.addAll(invisibleEnvironments);
}
for (FieldNode field : node.fields) {
if (field.visibleAnnotations == null) {
field.visibleAnnotations = new ArrayList<>();
}

Collection<AnnotationNode> invisibleEnvironments;
if (field.invisibleAnnotations != null) {
invisibleEnvironments = field.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + onlyIn + ";"))
.collect(Collectors.toList());
field.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
field.visibleAnnotations.addAll(invisibleEnvironments);
}
for (MethodNode method : node.methods) {
if (method.visibleAnnotations == null) {
method.visibleAnnotations = new ArrayList<>();
}

Collection<AnnotationNode> invisibleEnvironments;
if (method.invisibleAnnotations != null) {
invisibleEnvironments = method.invisibleAnnotations.stream()
.filter(annotation -> Objects.equals(annotation.desc, "L" + ENVIRONMENT + ";") || Objects.equals(annotation.desc, "L" + onlyIn + ";"))
.collect(Collectors.toList());
method.invisibleAnnotations.removeAll(invisibleEnvironments);
} else {
invisibleEnvironments = Collections.emptyList();
}
method.visibleAnnotations.addAll(invisibleEnvironments);
}
return node;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* This file is licensed under the MIT License, part of architectury-transformer.
* Copyright (c) 2020, 2021, 2022 architectury
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

package dev.architectury.transformer.transformers;

/**
* Handle @ForgeEvent and @ForgeEventCancellable and promote @Environment from being an invisible annotation to being an visible annotation.
*/
public class TransformNeoForgeAnnotations extends TransformForgeLikeAnnotations {
public TransformNeoForgeAnnotations() {
super(NEOFORGE_ONLY_IN);
}
}
Loading

0 comments on commit 0a64248

Please sign in to comment.