From 7e7c26fe9c6f39c75b13b56e8f5a040b6584cf7b Mon Sep 17 00:00:00 2001 From: Nils Gereke Date: Tue, 9 Dec 2025 23:57:00 +0100 Subject: [PATCH 1/2] feat: 1.21.11 support --- .github/workflows/buildtools.sh | 3 +- .../imprex/zip/common/MinecraftVersion.java | 3 +- zip-nms/pom.xml | 1 + zip-nms/zip-nms-v1_21_R7/pom.xml | 70 ++++++ .../zip/nms/v1_21_R7/ZipNmsManager.java | 214 ++++++++++++++++++ zip-plugin/pom.xml | 6 + 6 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 zip-nms/zip-nms-v1_21_R7/pom.xml create mode 100644 zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java diff --git a/.github/workflows/buildtools.sh b/.github/workflows/buildtools.sh index 29a1243..4c348b0 100644 --- a/.github/workflows/buildtools.sh +++ b/.github/workflows/buildtools.sh @@ -37,4 +37,5 @@ checkVersion "1.21.3" "21" checkVersion "1.21.4" "21" checkVersion "1.21.5" "21" checkVersion "1.21.6" "21" -checkVersion "1.21.9" "21" \ No newline at end of file +checkVersion "1.21.10" "21" +checkVersion "1.21.11" "21" \ No newline at end of file diff --git a/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java b/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java index 590d089..2550e93 100644 --- a/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java +++ b/zip-common/src/main/java/net/imprex/zip/common/MinecraftVersion.java @@ -18,7 +18,8 @@ private static final class NmsMapping { private static final List MAPPINGS = new ArrayList<>(); static { - MAPPINGS.add(new NmsMapping("1.21.9", "v1_21_R6")); + MAPPINGS.add(new NmsMapping("1.21.11", "v1_21_R7")); + MAPPINGS.add(new NmsMapping("1.21.10", "v1_21_R6")); MAPPINGS.add(new NmsMapping("1.21.6", "v1_21_R5")); MAPPINGS.add(new NmsMapping("1.21.5", "v1_21_R4")); MAPPINGS.add(new NmsMapping("1.21.4", "v1_21_R3")); diff --git a/zip-nms/pom.xml b/zip-nms/pom.xml index 896168c..75ca266 100644 --- a/zip-nms/pom.xml +++ b/zip-nms/pom.xml @@ -25,5 +25,6 @@ zip-nms-v1_21_R4 zip-nms-v1_21_R5 zip-nms-v1_21_R6 + zip-nms-v1_21_R7 \ No newline at end of file diff --git a/zip-nms/zip-nms-v1_21_R7/pom.xml b/zip-nms/zip-nms-v1_21_R7/pom.xml new file mode 100644 index 0000000..773b0bd --- /dev/null +++ b/zip-nms/zip-nms-v1_21_R7/pom.xml @@ -0,0 +1,70 @@ + + 4.0.0 + + + net.imprex + zip-nms + ${revision} + + + zip-nms-v1_21_R7 + + + + net.imprex + zip-nms-api + ${revision} + provided + + + org.spigotmc + spigot + 1.21.11-R0.1-SNAPSHOT + remapped-mojang + provided + + + + + + + net.md-5 + specialsource-maven-plugin + ${plugin.specialsource.version} + + + package + + remap + + remap-obf + + + org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:txt:maps-mojang + true + + org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-mojang + true + remapped-obf + + + + package + + remap + + remap-spigot + + + ${project.build.directory}/${project.artifactId}-${project.version}-remapped-obf.jar + + org.spigotmc:minecraft-server:1.21.11-R0.1-SNAPSHOT:csrg:maps-spigot + + org.spigotmc:spigot:1.21.11-R0.1-SNAPSHOT:jar:remapped-obf + + + + + + + \ No newline at end of file diff --git a/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java new file mode 100644 index 0000000..77c787e --- /dev/null +++ b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java @@ -0,0 +1,214 @@ +package net.imprex.zip.nms.v1_21_R7; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.UUID; +import java.util.function.BiConsumer; + +import org.bukkit.Material; +import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; + +import com.google.common.collect.Multimaps; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.mojang.authlib.GameProfile; +import com.mojang.authlib.properties.Property; +import com.mojang.authlib.properties.PropertyMap; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Dynamic; +import com.mojang.serialization.DynamicOps; +import com.mojang.serialization.JsonOps; + +import net.imprex.zip.common.BPConstants; +import net.imprex.zip.common.ReflectionUtil; +import net.imprex.zip.nms.api.ItemStackContainerResult; +import net.imprex.zip.nms.api.ItemStackWithSlot; +import net.imprex.zip.nms.api.NmsManager; +import net.minecraft.SharedConstants; +import net.minecraft.core.RegistryAccess; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.ListTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtIo; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.server.MinecraftServer; +import net.minecraft.util.datafix.DataFixers; +import net.minecraft.util.datafix.fixes.References; +import net.minecraft.world.item.component.ResolvableProfile; + +public class ZipNmsManager implements NmsManager { + + private static final int DATA_VERSION = SharedConstants.getCurrentVersion().dataVersion().version(); + + @SuppressWarnings("deprecation") + private static final RegistryAccess DEFAULT_REGISTRY = MinecraftServer.getServer().registryAccess(); + + private static final DynamicOps DYNAMIC_OPS_NBT = DEFAULT_REGISTRY.createSerializationContext(NbtOps.INSTANCE); + private static final DynamicOps DYNAMIC_OPS_JSON = DEFAULT_REGISTRY.createSerializationContext(JsonOps.INSTANCE); + + private static final BiConsumer SET_PROFILE; + + static { + BiConsumer setProfile = (meta, profile) -> { + throw new NullPointerException("Unable to find 'setProfile' method!"); + }; + + Class craftMetaSkullClass = new ItemStack(Material.PLAYER_HEAD) + .getItemMeta() + .getClass(); + + Method setResolvableProfileMethod = ReflectionUtil.searchMethod(craftMetaSkullClass, void.class, ResolvableProfile.class); + if (setResolvableProfileMethod != null) { + setProfile = (meta, profile) -> { + try { + setResolvableProfileMethod.invoke(meta, ResolvableProfile.createResolved(profile)); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + }; + } else { + Method setProfileMethod = ReflectionUtil.searchMethod(craftMetaSkullClass, void.class, GameProfile.class); + if (setProfileMethod != null) { + setProfile = (meta, profile) -> { + try { + setProfileMethod.invoke(meta, profile); + } catch (IllegalAccessException | InvocationTargetException e) { + e.printStackTrace(); + } + }; + } + } + + SET_PROFILE = setProfile; + } + + @Override + public JsonObject itemstackToJsonElement(ItemStack[] items) { + JsonArray jsonItems = new JsonArray(); + for (int slot = 0; slot < items.length; slot++) { + ItemStack item = items[slot]; + if (item == null || item.getType() == Material.AIR) { + continue; + } + net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, slot); + jsonItems.add(resultJson); + } + + JsonObject outputJson = new JsonObject(); + outputJson.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + outputJson.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, items.length); + outputJson.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return outputJson; + } + + @Override + public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { + // check if current version the same + if (json.get(BPConstants.KEY_INVENTORY_VERSION).getAsInt() != BPConstants.INVENTORY_VERSION) { + throw new IllegalStateException("Unable to convert binary to itemstack because zip version is missmatching"); + } + + int dataVersion = json.get(BPConstants.KEY_INVENTORY_DATA_VERSION).getAsInt(); + int itemsSize = json.get(BPConstants.KEY_INVENTORY_ITEMS_SIZE).getAsInt(); + + List items = new ArrayList<>(); + + JsonArray jsonItems = json.get(BPConstants.KEY_INVENTORY_ITEMS).getAsJsonArray(); + for (JsonElement item : jsonItems) { + Dynamic dynamicItem = new Dynamic<>(JsonOps.INSTANCE, item); + Dynamic dynamicItemFixed = DataFixers.getDataFixer() + .update(References.ITEM_STACK, dynamicItem, dataVersion, DATA_VERSION); + + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) + .getOrThrow(); + + ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); + + items.add(new ItemStackWithSlot(slot, bukkitItem)); + } + + return new ItemStackContainerResult(itemsSize, items); + } + + @Override + public JsonObject migrateToJsonElement(byte[] binary) { + CompoundTag compound; + try (ByteArrayInputStream inputStream = new ByteArrayInputStream(binary)) { + compound = NbtIo.readCompressed(inputStream, NbtAccounter.unlimitedHeap()); + } catch (IOException e) { + throw new IllegalStateException("Unable to parse binary to nbt", e); + } + + ListTag list = compound.getListOrEmpty("i"); + + int currentSlot = 0; + + JsonArray jsonItems = new JsonArray(); + for (Tag base : list) { + if (base instanceof CompoundTag itemTag) { + String itemType = itemTag.getString("id").orElse(""); + if (itemType.equals("minecraft:air")) { + currentSlot++; + continue; + } + + Dynamic dynamicItem = new Dynamic<>(NbtOps.INSTANCE, itemTag); + net.minecraft.world.item.ItemStack minecraftItem = net.minecraft.world.item.ItemStack.CODEC + .parse(DYNAMIC_OPS_NBT, dynamicItem.getValue()) + .getOrThrow(); + + DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); + JsonObject resultJson = result.getOrThrow().getAsJsonObject(); + + resultJson.addProperty(BPConstants.KEY_INVENTORY_SLOT, currentSlot); + jsonItems.add(resultJson); + + currentSlot++; + } + } + + JsonObject json = new JsonObject(); + json.addProperty(BPConstants.KEY_INVENTORY_VERSION, BPConstants.INVENTORY_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_DATA_VERSION, DATA_VERSION); + json.addProperty(BPConstants.KEY_INVENTORY_ITEMS_SIZE, list.size()); + json.add(BPConstants.KEY_INVENTORY_ITEMS, jsonItems); + return json; + } + + @Override + public void setSkullProfile(SkullMeta meta, String texture) { + try { + HashMap properties = new HashMap<>(); + properties.put("textures", new Property("textures", texture)); + + PropertyMap propertyMap = new PropertyMap(Multimaps.forMap(properties)); + GameProfile gameProfile = new GameProfile(UUID.randomUUID(), "", propertyMap); + + SET_PROFILE.accept(meta, gameProfile); + } catch (Exception e) { + e.printStackTrace(); + } + } + + @Override + public boolean isAir(Material material) { + return material == null || material == Material.AIR; + } +} \ No newline at end of file diff --git a/zip-plugin/pom.xml b/zip-plugin/pom.xml index cd7515d..4b30c71 100644 --- a/zip-plugin/pom.xml +++ b/zip-plugin/pom.xml @@ -125,5 +125,11 @@ ${revision} compile + + net.imprex + zip-nms-v1_21_R7 + ${revision} + compile + \ No newline at end of file From e76a5883a9e825686f4aca8cd1f3372ef858d7fb Mon Sep 17 00:00:00 2001 From: Nils Gereke Date: Wed, 10 Dec 2025 01:15:42 +0100 Subject: [PATCH 2/2] feat: 1.21.11 mojang mapping support --- pom.xml | 29 ++++++++--- .../net/imprex/zip/common/ServerVersion.java | 48 +++++++++++++++++ zip-nms/zip-nms-v1_21_R7/pom.xml | 16 +++++- .../zip/nms/v1_21_R7/ZipNmsManager.java | 52 +++++++++++++++++-- zip-plugin/pom.xml | 7 +++ .../main/java/net/imprex/zip/NmsInstance.java | 8 ++- 6 files changed, 147 insertions(+), 13 deletions(-) create mode 100644 zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java diff --git a/pom.xml b/pom.xml index 4719b15..fd9968e 100644 --- a/pom.xml +++ b/pom.xml @@ -26,6 +26,7 @@ 3.10.1 3.4.0 + 3.4.2 1.2.7 2.0.2 @@ -51,14 +52,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - ${plugin.compile.version} - - 17 - - org.apache.maven.plugins maven-shade-plugin @@ -90,6 +83,26 @@ + + org.apache.maven.plugins + maven-jar-plugin + ${plugin.jar.version} + + + + mojang + + + + + + org.apache.maven.plugins + maven-compiler-plugin + ${plugin.compile.version} + + 17 + + org.codehaus.mojo flatten-maven-plugin diff --git a/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java b/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java new file mode 100644 index 0000000..cacf07d --- /dev/null +++ b/zip-common/src/main/java/net/imprex/zip/common/ServerVersion.java @@ -0,0 +1,48 @@ +/** + * @author Imprex-Development + * @see ServerVersion.java + */ +package net.imprex.zip.common; + +public class ServerVersion { + + private static final boolean IS_MOJANG_MAPPED = classExists("net.minecraft.core.BlockPos") + && fieldExists("net.minecraft.world.level.block.Blocks", "AIR"); + private static final boolean IS_FOLIA = classExists("io.papermc.paper.threadedregions.RegionizedServer"); + private static final boolean IS_PAPER = !IS_FOLIA && classExists("com.destroystokyo.paper.PaperConfig"); + private static final boolean IS_BUKKIT = !IS_FOLIA && !IS_PAPER; + + private static boolean classExists(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } + + private static boolean fieldExists(String className, String fieldName) { + try { + Class target = Class.forName(className); + return target.getDeclaredField(fieldName) != null; + } catch (Exception e) { + return false; + } + } + + public static boolean isMojangMapped() { + return IS_MOJANG_MAPPED; + } + + public static boolean isFolia() { + return IS_FOLIA; + } + + public static boolean isPaper() { + return IS_PAPER; + } + + public static boolean isBukkit() { + return IS_BUKKIT; + } +} diff --git a/zip-nms/zip-nms-v1_21_R7/pom.xml b/zip-nms/zip-nms-v1_21_R7/pom.xml index 773b0bd..f5fa515 100644 --- a/zip-nms/zip-nms-v1_21_R7/pom.xml +++ b/zip-nms/zip-nms-v1_21_R7/pom.xml @@ -1,5 +1,5 @@ - 4.0.0 + 4.0.0 net.imprex @@ -27,6 +27,20 @@ + + org.apache.maven.plugins + maven-shade-plugin + + true + mojang-mapped + + + net.imprex.zip.nms.v1_21_R7 + net.imprex.zip.nms.v1_21_R7_mojang + + + + net.md-5 specialsource-maven-plugin diff --git a/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java index 77c787e..b28bc29 100644 --- a/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java +++ b/zip-nms/zip-nms-v1_21_R7/src/main/java/net/imprex/zip/nms/v1_21_R7/ZipNmsManager.java @@ -10,8 +10,8 @@ import java.util.UUID; import java.util.function.BiConsumer; +import org.bukkit.Bukkit; import org.bukkit.Material; -import org.bukkit.craftbukkit.v1_21_R7.inventory.CraftItemStack; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.SkullMeta; @@ -57,6 +57,9 @@ public class ZipNmsManager implements NmsManager { private static final BiConsumer SET_PROFILE; + private static final Method CRAFT_ITEM_STACK_AS_NMS_COPY; + private static final Method CRAFT_ITEM_STACK_AS_CRAFT_MIRROR; + static { BiConsumer setProfile = (meta, profile) -> { throw new NullPointerException("Unable to find 'setProfile' method!"); @@ -89,6 +92,49 @@ public class ZipNmsManager implements NmsManager { } SET_PROFILE = setProfile; + + String craftItemStackClass = Bukkit.getServer().getClass().getPackageName() + ".inventory.CraftItemStack"; + // CraftItemStack.asNMSCopy(item) + try { + Class craftItemStack = Class.forName(craftItemStackClass); + Method method = ReflectionUtil.searchMethod( + craftItemStack, + net.minecraft.world.item.ItemStack.class, + ItemStack.class); + method.setAccessible(true); + CRAFT_ITEM_STACK_AS_NMS_COPY = method; + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + + // CraftItemStack.asCraftMirror(item) + try { + Class craftItemStack = Class.forName(craftItemStackClass); + Method method = ReflectionUtil.searchMethod( + craftItemStack, + craftItemStack, + net.minecraft.world.item.ItemStack.class); + method.setAccessible(true); + CRAFT_ITEM_STACK_AS_CRAFT_MIRROR = method; + } catch (ClassNotFoundException e) { + throw new IllegalStateException(e); + } + } + + private static net.minecraft.world.item.ItemStack asNmsCopy(ItemStack item) { + try { + return (net.minecraft.world.item.ItemStack) CRAFT_ITEM_STACK_AS_NMS_COPY.invoke(null, item); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalStateException(e); + } + } + + private static ItemStack asCraftMirror(net.minecraft.world.item.ItemStack item) { + try { + return (ItemStack) CRAFT_ITEM_STACK_AS_CRAFT_MIRROR.invoke(null, item); + } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) { + throw new IllegalStateException(e); + } } @Override @@ -99,7 +145,7 @@ public JsonObject itemstackToJsonElement(ItemStack[] items) { if (item == null || item.getType() == Material.AIR) { continue; } - net.minecraft.world.item.ItemStack minecraftItem = CraftItemStack.asNMSCopy(item); + net.minecraft.world.item.ItemStack minecraftItem = asNmsCopy(item); DataResult result = net.minecraft.world.item.ItemStack.CODEC.encodeStart(DYNAMIC_OPS_JSON, minecraftItem); JsonObject resultJson = result.getOrThrow().getAsJsonObject(); @@ -138,7 +184,7 @@ public ItemStackContainerResult jsonElementToItemStack(JsonObject json) { .parse(DYNAMIC_OPS_JSON, dynamicItemFixed.getValue()) .getOrThrow(); - ItemStack bukkitItem = CraftItemStack.asCraftMirror(minecraftItem); + ItemStack bukkitItem = asCraftMirror(minecraftItem); int slot = item.getAsJsonObject().get(BPConstants.KEY_INVENTORY_SLOT).getAsInt(); items.add(new ItemStackWithSlot(slot, bukkitItem)); diff --git a/zip-plugin/pom.xml b/zip-plugin/pom.xml index 4b30c71..38f6ca5 100644 --- a/zip-plugin/pom.xml +++ b/zip-plugin/pom.xml @@ -131,5 +131,12 @@ ${revision} compile + + net.imprex + zip-nms-v1_21_R7 + ${revision} + mojang-mapped + compile + \ No newline at end of file diff --git a/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java b/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java index 1232970..4e8abd1 100644 --- a/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java +++ b/zip-plugin/src/main/java/net/imprex/zip/NmsInstance.java @@ -10,20 +10,26 @@ import com.google.gson.JsonObject; import net.imprex.zip.common.MinecraftVersion; +import net.imprex.zip.common.ServerVersion; import net.imprex.zip.common.ZIPLogger; import net.imprex.zip.nms.api.ItemStackContainerResult; import net.imprex.zip.nms.api.NmsManager; public class NmsInstance { - + private static NmsManager instance; public static void initialize() { + if (NmsInstance.instance != null) { throw new IllegalStateException("NMS adapter is already initialized!"); } String nmsVersion = MinecraftVersion.nmsVersion(); + if (ServerVersion.isMojangMapped()) { + nmsVersion += "_mojang"; + } + ZIPLogger.info("Searching NMS adapter for server version \"" + nmsVersion + "\"!"); try {