Skip to content

Commit e4b6e87

Browse files
committed
Experimental: enable WorldEdit support for Bukkit to support complex plot world generation; for IzzelAliz#1641
1 parent 846e82b commit e4b6e87

File tree

6 files changed

+146
-89
lines changed

6 files changed

+146
-89
lines changed

arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRemapper.java

+13
Original file line numberDiff line numberDiff line change
@@ -55,15 +55,18 @@ public class ArclightRemapper {
5555

5656
private final JarMapping toNmsMapping;
5757
private final JarMapping toBukkitMapping;
58+
private final JarMapping fromMojMapping;
5859
public final InheritanceMap inheritanceMap;
5960
private final List<PluginTransformer> transformerList = new ArrayList<>();
6061
private final JarRemapper toBukkitRemapper;
6162
private final JarRemapper toNmsRemapper;
63+
private final JarRemapper fromMojRemapper;
6264
private final List<PluginPatcher> patchers;
6365

6466
public ArclightRemapper() throws Exception {
6567
this.toNmsMapping = new JarMapping();
6668
this.toBukkitMapping = new JarMapping();
69+
this.fromMojMapping = new JarMapping();
6770
this.inheritanceMap = new InheritanceMap();
6871
this.toNmsMapping.loadMappings(
6972
new BufferedReader(new InputStreamReader(ArclightRemapper.class.getResourceAsStream("/bukkit_srg.srg"))),
@@ -73,6 +76,10 @@ public ArclightRemapper() throws Exception {
7376
new BufferedReader(new InputStreamReader(ArclightRemapper.class.getResourceAsStream("/bukkit_srg.srg"))),
7477
null, null, true
7578
);
79+
this.fromMojMapping.loadMappings(
80+
new BufferedReader(new InputStreamReader(ArclightRemapper.class.getResourceAsStream("/bukkit_moj.srg"))),
81+
null, null, true
82+
);
7683
BiMap<String, String> inverseClassMap = HashBiMap.create(toNmsMapping.classes).inverse();
7784
try (BufferedReader reader = new BufferedReader(new InputStreamReader(ArclightRemapper.class.getResourceAsStream("/inheritanceMap.txt")))) {
7885
inheritanceMap.load(reader, inverseClassMap);
@@ -82,6 +89,7 @@ public ArclightRemapper() throws Exception {
8289
inheritanceProvider.add(new ClassLoaderProvider(ClassLoader.getSystemClassLoader()));
8390
this.toNmsMapping.setFallbackInheritanceProvider(inheritanceProvider);
8491
this.toBukkitMapping.setFallbackInheritanceProvider(inheritanceProvider);
92+
this.fromMojMapping.setFallbackInheritanceProvider(inheritanceProvider);
8593
this.transformerList.add(ArclightInterfaceInvokerGen.INSTANCE);
8694
this.transformerList.add(ArclightRedirectAdapter.INSTANCE);
8795
this.transformerList.add(ClassLoaderAdapter.INSTANCE);
@@ -92,6 +100,7 @@ public ArclightRemapper() throws Exception {
92100
toBukkitMapping.setFallbackInheritanceProvider(GlobalClassRepo.inheritanceProvider());
93101
this.toBukkitRemapper = new LenientJarRemapper(toBukkitMapping);
94102
this.toNmsRemapper = new LenientJarRemapper(toNmsMapping);
103+
this.fromMojRemapper = new LenientJarRemapper(fromMojMapping);
95104
RemapSourceHandler.register();
96105
}
97106

@@ -107,6 +116,10 @@ public static JarRemapper getNmsMapper() {
107116
return INSTANCE.toNmsRemapper;
108117
}
109118

119+
public static JarRemapper getMojRemapper() {
120+
return INSTANCE.fromMojRemapper;
121+
}
122+
110123
public List<PluginTransformer> getTransformerList() {
111124
return transformerList;
112125
}

arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/patcher/integrated/IntegratedPatcher.java

+6-2
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,18 @@
99
import java.util.Map;
1010
import java.util.function.BiConsumer;
1111

12+
/*
13+
* This can be useful, so look at what I've found!
14+
*/
1215
public class IntegratedPatcher implements PluginPatcher {
1316

1417
private static final Map<String, BiConsumer<ClassNode, ClassRepo>> SPECIFIC = new HashMap<>() {};
1518
private static final List<BiConsumer<ClassNode, ClassRepo>> GENERAL = new ArrayList<>();
1619

1720
static {
18-
SPECIFIC.put("com/sk89q/worldedit/bukkit/BukkitAdapter", WorldEdit::handleBukkitAdapter);
19-
SPECIFIC.put("com/sk89q/worldedit/bukkit/adapter/Refraction", WorldEdit::handlePickName);
21+
// Handle WorldEdit reflective using NMS
22+
// Their naming mapping is behind the version, syncing manually
23+
SPECIFIC.put("com/sk89q/worldedit/bukkit/adapter/impl/v1_21/StaticRefraction", WorldEdit::handleStaticRefraction);
2024
GENERAL.add(WorldEdit::handleWatchdog);
2125
}
2226

Original file line numberDiff line numberDiff line change
@@ -1,98 +1,18 @@
11
package io.izzel.arclight.common.mod.util.remapper.patcher.integrated;
22

33
import io.izzel.arclight.api.PluginPatcher;
4+
import io.izzel.arclight.common.mod.server.ArclightServer;
5+
import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper;
6+
import org.apache.logging.log4j.Logger;
47
import org.objectweb.asm.Opcodes;
58
import org.objectweb.asm.Type;
6-
import org.objectweb.asm.commons.GeneratorAdapter;
7-
import org.objectweb.asm.commons.Method;
8-
import org.objectweb.asm.tree.AbstractInsnNode;
9-
import org.objectweb.asm.tree.ClassNode;
10-
import org.objectweb.asm.tree.InsnList;
11-
import org.objectweb.asm.tree.InsnNode;
12-
import org.objectweb.asm.tree.MethodInsnNode;
13-
import org.objectweb.asm.tree.MethodNode;
14-
import org.objectweb.asm.tree.TypeInsnNode;
15-
import org.objectweb.asm.tree.VarInsnNode;
9+
import org.objectweb.asm.tree.*;
1610

17-
import java.util.Locale;
11+
import java.util.HashMap;
12+
import java.util.Map;
1813

1914
public class WorldEdit {
20-
21-
public static void handleBukkitAdapter(ClassNode node, PluginPatcher.ClassRepo repo) {
22-
MethodNode standardize = new MethodNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_SYNTHETIC, "patcher$standardize",
23-
Type.getMethodDescriptor(Type.getType(String.class), Type.getType(String.class)), null, null);
24-
try {
25-
GeneratorAdapter adapter = new GeneratorAdapter(standardize, standardize.access, standardize.name, standardize.desc);
26-
adapter.loadArg(0);
27-
adapter.push(':');
28-
adapter.push('_');
29-
adapter.invokeVirtual(Type.getType(String.class), Method.getMethod(String.class.getMethod("replace", char.class, char.class)));
30-
adapter.push("\\s+");
31-
adapter.push("_");
32-
adapter.invokeVirtual(Type.getType(String.class), Method.getMethod(String.class.getMethod("replaceAll", String.class, String.class)));
33-
adapter.push("\\W");
34-
adapter.push("");
35-
adapter.invokeVirtual(Type.getType(String.class), Method.getMethod(String.class.getMethod("replaceAll", String.class, String.class)));
36-
adapter.getStatic(Type.getType(Locale.class), "ENGLISH", Type.getType(Locale.class));
37-
adapter.invokeVirtual(Type.getType(String.class), Method.getMethod(String.class.getMethod("toUpperCase", Locale.class)));
38-
adapter.returnValue();
39-
adapter.endMethod();
40-
} catch (Throwable t) {
41-
t.printStackTrace();
42-
}
43-
node.methods.add(standardize);
44-
for (MethodNode method : node.methods) {
45-
if (method.name.equals("adapt")) {
46-
handleAdapt(node, standardize, method);
47-
}
48-
}
49-
}
50-
51-
public static void handlePickName(ClassNode node, PluginPatcher.ClassRepo repo) {
52-
for (MethodNode method : node.methods) {
53-
if (method.name.equals("pickName")) {
54-
method.instructions.clear();
55-
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 1));
56-
method.instructions.add(new InsnNode(Opcodes.ARETURN));
57-
return;
58-
}
59-
}
60-
}
61-
62-
private static void handleAdapt(ClassNode node, MethodNode standardize, MethodNode method) {
63-
switch (method.desc) {
64-
case "(Lcom/sk89q/worldedit/world/item/ItemType;)Lorg/bukkit/Material;":
65-
case "(Lcom/sk89q/worldedit/world/block/BlockType;)Lorg/bukkit/Material;":
66-
case "(Lcom/sk89q/worldedit/world/biome/BiomeType;)Lorg/bukkit/block/Biome;":
67-
case "(Lcom/sk89q/worldedit/world/entity/EntityType;)Lorg/bukkit/entity/EntityType;": {
68-
for (AbstractInsnNode instruction : method.instructions) {
69-
if (instruction.getOpcode() == Opcodes.ATHROW) {
70-
InsnList list = new InsnList();
71-
list.add(new VarInsnNode(Opcodes.ALOAD, 0));
72-
list.add(new MethodInsnNode(Opcodes.INVOKEVIRTUAL, Type.getMethodType(method.desc).getArgumentTypes()[0].getInternalName(), "getId", "()Ljava/lang/String;", false));
73-
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, node.name, standardize.name, standardize.desc, false));
74-
switch (Type.getMethodType(method.desc).getReturnType().getInternalName()) {
75-
case "org/bukkit/Material":
76-
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/bukkit/Material", "getMaterial", "(Ljava/lang/String;)Lorg/bukkit/Material;", false));
77-
break;
78-
case "org/bukkit/block/Biome":
79-
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/bukkit/block/Biome", "valueOf", "(Ljava/lang/String;)Lorg/bukkit/block/Biome;", false));
80-
break;
81-
case "org/bukkit/entity/EntityType":
82-
list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, "org/bukkit/entity/EntityType", "fromName", "(Ljava/lang/String;)Lorg/bukkit/entity/EntityType;", false));
83-
break;
84-
}
85-
list.add(new InsnNode(Opcodes.ARETURN));
86-
method.instructions.insert(instruction, list);
87-
method.instructions.set(instruction, new InsnNode(Opcodes.POP));
88-
return;
89-
}
90-
}
91-
break;
92-
}
93-
}
94-
}
95-
15+
// Don't use SpigotWatchdog since we're not using it
9616
public static void handleWatchdog(ClassNode node, PluginPatcher.ClassRepo repo) {
9717
if (node.interfaces.size() == 1 && node.interfaces.get(0).equals("com/sk89q/worldedit/extension/platform/Watchdog")
9818
&& node.name.contains("SpigotWatchdog")) {
@@ -110,4 +30,117 @@ public static void handleWatchdog(ClassNode node, PluginPatcher.ClassRepo repo)
11030
}
11131
}
11232
}
33+
34+
// Correctly handle reflection name picking
35+
// Their naming mapping for NMS is somehow behind the version
36+
public static void handleStaticRefraction(ClassNode node, PluginPatcher.ClassRepo repo) {
37+
ArclightServer.LOGGER.warn("Loading WorldEdit Bukkit support for 1.21.1 ...");
38+
ArclightServer.LOGGER.warn("For WorldEdit (on bukkit) compatibility issues, please report to us in advance!");
39+
var remapper = ArclightRemapper.getMojRemapper();
40+
var addEntity = remapper.mapMethodName(
41+
"net/minecraft/server/level/ServerLevel",
42+
"addFreshEntity",
43+
"(Lnet/minecraft/world/entity/Entity;)Z",
44+
Opcodes.ACC_PUBLIC
45+
);
46+
47+
var mapped = Map.of(
48+
"getChunkFutureMainThread", remapper.mapMethodName(
49+
"net/minecraft/server/level/ServerChunkCache",
50+
"getChunkFutureMainThread",
51+
"(IILnet/minecraft/world/level/chunk/status/ChunkStatus;Z)Ljava/util/concurrent/CompletableFuture;",
52+
Opcodes.ACC_PRIVATE
53+
),
54+
"mainThreadProcessor", remapper.mapFieldName(
55+
"net/minecraft/server/level/ServerChunkCache",
56+
"mainThreadProcessor",
57+
"Lnet/minecraft/server/level/ServerChunkCache$MainThreadExecutor;",
58+
Opcodes.ACC_PRIVATE | Opcodes.ACC_FINAL
59+
),
60+
"nextTickTime", remapper.mapFieldName(
61+
"net/minecraft/server/MinecraftServer",
62+
"nextTickTimeNanos",
63+
"J",
64+
Opcodes.ACC_PRIVATE
65+
),
66+
"getBlockEntity", remapper.mapMethodName(
67+
"net/minecraft/world/level/BlockGetter",
68+
"getBlockEntity",
69+
"(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/entity/BlockEntity;",
70+
Opcodes.ACC_PUBLIC
71+
),
72+
"addFreshEntity", addEntity,
73+
"addFreshEntityWithPassengers", addEntity,
74+
"getBlockState", remapper.mapMethodName(
75+
"net/minecraft/world/level/Level",
76+
"getBlockState",
77+
"(Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/level/block/state/BlockState;",
78+
Opcodes.ACC_PUBLIC
79+
),
80+
"setBlock", remapper.mapMethodName(
81+
"net/minecraft/world/level/LevelWriter",
82+
"setBlock",
83+
"(Lnet/minecraft/core/BlockPos;Lnet/minecraft/world/level/block/state/BlockState;I)Z",
84+
Opcodes.ACC_PUBLIC
85+
),
86+
"removeBlock", remapper.mapMethodName(
87+
"net/minecraft/world/level/Level",
88+
"removeBlock",
89+
"(Lnet/minecraft/core/BlockPos;Z)Z",
90+
Opcodes.ACC_PUBLIC
91+
),
92+
"destroyBlock", remapper.mapMethodName(
93+
"net/minecraft/world/level/Level",
94+
"destroyBlock",
95+
"(Lnet/minecraft/core/BlockPos;ZLnet/minecraft/world/entity/Entity;I)Z",
96+
Opcodes.ACC_PUBLIC
97+
)
98+
);
99+
for (MethodNode method : node.methods) {
100+
if ("<clinit>".equals(method.name)) {
101+
boolean isLastPut = true;
102+
LdcInsnNode lastLdc = null;
103+
Map<String, String> fieldToProvided = new HashMap<>();
104+
for(var insn: method.instructions) {
105+
if (isLastPut && insn instanceof LdcInsnNode ldc && ldc.cst instanceof String) {
106+
lastLdc = ldc;
107+
isLastPut = false;
108+
}
109+
if (insn instanceof FieldInsnNode field) {
110+
fieldToProvided.put(field.name, (String) lastLdc.cst);
111+
isLastPut = true;
112+
}
113+
}
114+
method.instructions.clear();
115+
116+
int line = 0;
117+
for (var entry: fieldToProvided.entrySet()) {
118+
var label = new LabelNode();
119+
method.instructions.add(label);
120+
method.instructions.add(new LineNumberNode(--line, label));
121+
method.instructions.add(new LdcInsnNode(mapped.get(entry.getValue())));
122+
method.instructions.add(new FieldInsnNode(
123+
Opcodes.PUTSTATIC,
124+
node.name,
125+
entry.getKey(),
126+
"Ljava/lang/String;"
127+
));
128+
}
129+
130+
var label = new LabelNode();
131+
method.instructions.add(label);
132+
method.instructions.add(new LineNumberNode(--line, label));
133+
method.instructions.add(new FieldInsnNode(Opcodes.GETSTATIC, Type.getInternalName(ArclightServer.class), "LOGGER", Type.getDescriptor(Logger.class)));
134+
method.instructions.add(new InsnNode(Opcodes.DUP));
135+
method.instructions.add(new LdcInsnNode("Arclight is enabling support for WorldEdit!"));
136+
method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Logger.class), "warn", "(Ljava/lang/String;)V", true));
137+
method.instructions.add(new LdcInsnNode("For issues with WorldEdit please report to Arclight."));
138+
method.instructions.add(new MethodInsnNode(Opcodes.INVOKEINTERFACE, Type.getInternalName(Logger.class), "warn", "(Ljava/lang/String;)V", true));
139+
140+
method.instructions.add(new InsnNode(Opcodes.RETURN));
141+
142+
method.visitMaxs(2, 0);
143+
}
144+
}
145+
}
113146
}

arclight-fabric/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,9 @@ tasks.register('fabricMappings', Copy) {
7878
from(arclight.mappingsConfiguration.bukkitToFabric) {
7979
rename { name -> 'bukkit_srg.srg' }
8080
}
81+
from(arclight.mappingsConfiguration.bukkitToNeoForge) {
82+
rename { name -> 'bukkit_moj.srg' }
83+
}
8184
from(arclight.mappingsConfiguration.bukkitToFabricInheritance) {
8285
rename { name -> 'inheritanceMap.txt' }
8386
}

arclight-forge/build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ tasks.register('forgeMappings', Copy) {
8383
destinationDir = file("build/forge_mappings")
8484
from arclight.mappingsConfiguration.bukkitToForge
8585
from arclight.mappingsConfiguration.bukkitToForgeInheritance
86+
from arclight.mappingsConfiguration.bukkitToNeoForge
8687
}
8788

8889
project.sourceSets.main.output.dir file("build/forge_mappings"), builtBy: tasks.forgeMappings

arclight-neoforge/build.gradle

+3
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,9 @@ tasks.register('neoforgeMappings', Copy) {
7777
from(arclight.mappingsConfiguration.bukkitToNeoForge) {
7878
rename { name -> 'bukkit_srg.srg' }
7979
}
80+
from(arclight.mappingsConfiguration.bukkitToNeoForge) {
81+
rename { name -> 'bukkit_moj.srg'}
82+
}
8083
from arclight.mappingsConfiguration.bukkitToForgeInheritance
8184
}
8285

0 commit comments

Comments
 (0)