From 09ed5acec8329cce05e0ceea7600607f1a51a5ba Mon Sep 17 00:00:00 2001 From: Lixfel Date: Wed, 1 Apr 2020 19:08:38 +0200 Subject: [PATCH] Fixing 1.15 .schem loading --- .../src/de/steamwar/sql/Schematic_14.java | 242 +++++++++++++++++- 1 file changed, 241 insertions(+), 1 deletion(-) diff --git a/SpigotCore_14/src/de/steamwar/sql/Schematic_14.java b/SpigotCore_14/src/de/steamwar/sql/Schematic_14.java index 6e60d1c..2e02468 100644 --- a/SpigotCore_14/src/de/steamwar/sql/Schematic_14.java +++ b/SpigotCore_14/src/de/steamwar/sql/Schematic_14.java @@ -1,11 +1,17 @@ package de.steamwar.sql; import com.google.common.collect.ImmutableList; +import com.google.common.collect.Maps; import com.sk89q.jnbt.*; import com.sk89q.worldedit.EmptyClipboardException; +import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.WorldEditException; import com.sk89q.worldedit.bukkit.WorldEditPlugin; +import com.sk89q.worldedit.extension.input.InputParseException; +import com.sk89q.worldedit.extension.input.ParserContext; import com.sk89q.worldedit.extension.platform.Actor; +import com.sk89q.worldedit.extension.platform.Capability; +import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.extent.clipboard.BlockArrayClipboard; import com.sk89q.worldedit.extent.clipboard.Clipboard; import com.sk89q.worldedit.extent.clipboard.io.BuiltInClipboardFormat; @@ -19,6 +25,7 @@ import com.sk89q.worldedit.regions.Region; import com.sk89q.worldedit.session.ClipboardHolder; import com.sk89q.worldedit.world.DataFixer; import com.sk89q.worldedit.world.block.BlockState; +import com.sk89q.worldedit.world.block.BlockTypes; import com.sk89q.worldedit.world.registry.LegacyMapper; import org.bukkit.Bukkit; import org.bukkit.entity.Player; @@ -27,6 +34,7 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.util.*; +import java.util.stream.Collectors; import java.util.zip.GZIPInputStream; import static com.google.common.base.Preconditions.checkNotNull; @@ -77,7 +85,7 @@ class Schematic_14 { static Clipboard getClipboard(InputStream is, boolean schemFormat) throws IOException, NoClipboardException { try { if(schemFormat){ - return SCHEM.getReader(is).read(); + return new SpongeSchematicReader(new NBTInputStream(new GZIPInputStream(is))).read(); }else{ return new MCEditSchematicReader(new NBTInputStream(new GZIPInputStream(is))).read(); } @@ -345,6 +353,238 @@ class Schematic_14 { return LegacyMapper.getInstance().getBlockFromLegacy(id, data); } + @Override + public void close() throws IOException { + inputStream.close(); + } + } + private static class SpongeSchematicReader extends NBTSchematicReader { + + private final NBTInputStream inputStream; + private DataFixer fixer = null; + private int schematicVersion = -1; + private int dataVersion = -1; + private boolean faweSchem = false; + + /** + * Create a new instance. + * + * @param inputStream the input stream to read from + */ + public SpongeSchematicReader(NBTInputStream inputStream) { + checkNotNull(inputStream); + this.inputStream = inputStream; + } + + @Override + public Clipboard read() throws IOException { + CompoundTag schematicTag = getBaseTag(); + Map schematic = schematicTag.getValue(); + + final Platform platform = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING); + int liveDataVersion = platform.getDataVersion(); + + if (schematicVersion == 1) { + dataVersion = 1631; // this is a relatively safe assumption unless someone imports a schematic from 1.12, e.g. sponge 7.1- + fixer = platform.getDataFixer(); + return readVersion1(schematicTag); + } else if (schematicVersion == 2) { + dataVersion = requireTag(schematic, "DataVersion", IntTag.class).getValue(); + if (dataVersion < liveDataVersion) { + fixer = platform.getDataFixer(); + } + + return readVersion1(schematicTag); + } + throw new IOException("This schematic version is currently not supported"); + } + + @Override + public OptionalInt getDataVersion() { + try { + CompoundTag schematicTag = getBaseTag(); + Map schematic = schematicTag.getValue(); + if (schematicVersion == 1) { + return OptionalInt.of(1631); + } else if (schematicVersion == 2) { + return OptionalInt.of(requireTag(schematic, "DataVersion", IntTag.class).getValue()); + } + return OptionalInt.empty(); + } catch (IOException e) { + return OptionalInt.empty(); + } + } + + private CompoundTag getBaseTag() throws IOException { + NamedTag rootTag = inputStream.readNamedTag(); + if (!rootTag.getName().equals("Schematic")) { + throw new IOException("Tag 'Schematic' does not exist or is not first"); + } + CompoundTag schematicTag = (CompoundTag) rootTag.getTag(); + + // Check + Map schematic = schematicTag.getValue(); + + schematicVersion = requireTag(schematic, "Version", IntTag.class).getValue(); + return schematicTag; + } + + private BlockArrayClipboard readVersion1(CompoundTag schematicTag) throws IOException { + BlockVector3 origin; + Region region; + Map schematic = schematicTag.getValue(); + + int width = requireTag(schematic, "Width", ShortTag.class).getValue(); + int height = requireTag(schematic, "Height", ShortTag.class).getValue(); + int length = requireTag(schematic, "Length", ShortTag.class).getValue(); + + IntArrayTag offsetTag = getTag(schematic, "Offset", IntArrayTag.class); + int[] offsetParts; + if (offsetTag != null) { + offsetParts = offsetTag.getValue(); + if (offsetParts.length != 3) { + throw new IOException("Invalid offset specified in schematic."); + } + } else { + offsetParts = new int[] {0, 0, 0}; + } + + BlockVector3 min = BlockVector3.at(offsetParts[0], offsetParts[1], offsetParts[2]); + + CompoundTag metadataTag = getTag(schematic, "Metadata", CompoundTag.class); + int offsetX = 0; + int offsetY = 0; + int offsetZ = 0; + if (metadataTag != null && metadataTag.containsKey("WEOffsetX")) { + // We appear to have WorldEdit Metadata + Map metadata = metadataTag.getValue(); + offsetX = requireTag(metadata, "WEOffsetX", IntTag.class).getValue(); + offsetY = requireTag(metadata, "WEOffsetY", IntTag.class).getValue(); + offsetZ = requireTag(metadata, "WEOffsetZ", IntTag.class).getValue(); + BlockVector3 offset = BlockVector3.at(offsetX, offsetY, offsetZ); + origin = min.subtract(offset); + region = new CuboidRegion(min, min.add(width, height, length).subtract(BlockVector3.ONE)); + } else { + origin = min; + region = new CuboidRegion(origin, origin.add(width, height, length).subtract(BlockVector3.ONE)); + } + + IntTag paletteMaxTag = getTag(schematic, "PaletteMax", IntTag.class); + Map paletteObject = requireTag(schematic, "Palette", CompoundTag.class).getValue(); + if (paletteMaxTag != null && paletteObject.size() != paletteMaxTag.getValue()) { + throw new IOException("Block palette size does not match expected size."); + } + + Map palette = new HashMap<>(); + + ParserContext parserContext = new ParserContext(); + parserContext.setRestricted(false); + parserContext.setTryLegacy(false); + parserContext.setPreferringWildcard(false); + + for (String palettePart : paletteObject.keySet()) { + int id = requireTag(paletteObject, palettePart, IntTag.class).getValue(); + if (fixer != null) { + palettePart = fixer.fixUp(DataFixer.FixTypes.BLOCK_STATE, palettePart, dataVersion); + } + BlockState state; + try { + state = WorldEdit.getInstance().getBlockFactory().parseFromInput(palettePart, parserContext).toImmutableState(); + } catch (InputParseException e) { + state = BlockTypes.AIR.getDefaultState(); + } + palette.put(id, state); + } + + byte[] blocks = requireTag(schematic, "BlockData", ByteArrayTag.class).getValue(); + + Map> tileEntitiesMap = new HashMap<>(); + ListTag tileEntities = getTag(schematic, "BlockEntities", ListTag.class); + if (tileEntities == null) { + tileEntities = getTag(schematic, "TileEntities", ListTag.class); + } + if (tileEntities != null) { + List> tileEntityTags = tileEntities.getValue().stream() + .map(tag -> (CompoundTag) tag) + .map(CompoundTag::getValue) + .collect(Collectors.toList()); + + for (Map tileEntity : tileEntityTags) { + int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); + if(pos[0] < 0 || pos[0] >= width || pos[1] < 0 || pos[1] >= height || pos[2] < 0 || pos[2] >= length) + faweSchem = true; + } + + for (Map tileEntity : tileEntityTags) { + int[] pos = requireTag(tileEntity, "Pos", IntArrayTag.class).getValue(); + final BlockVector3 pt = BlockVector3.at(pos[0], pos[1], pos[2]); + Map values = Maps.newHashMap(tileEntity); + if(faweSchem){ + values.put("x", new IntTag(pt.getBlockX() - offsetX)); + values.put("y", new IntTag(pt.getBlockY() - offsetY)); + values.put("z", new IntTag(pt.getBlockZ() - offsetZ)); + }else{ + values.put("x", new IntTag(pt.getBlockX())); + values.put("y", new IntTag(pt.getBlockY())); + values.put("z", new IntTag(pt.getBlockZ())); + } + values.put("id", values.get("Id")); + values.remove("Id"); + values.remove("Pos"); + if (fixer != null) { + tileEntity = fixer.fixUp(DataFixer.FixTypes.BLOCK_ENTITY, new CompoundTag(values), dataVersion).getValue(); + } else { + tileEntity = values; + } + tileEntitiesMap.put(pt, tileEntity); + } + } + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + clipboard.setOrigin(origin); + + int index = 0; + int i = 0; + int value; + int varintLength; + while (i < blocks.length) { + value = 0; + varintLength = 0; + + while (true) { + value |= (blocks[i] & 127) << (varintLength++ * 7); + if (varintLength > 5) { + throw new IOException("VarInt too big (probably corrupted data)"); + } + if ((blocks[i] & 128) != 128) { + i++; + break; + } + i++; + } + // index = (y * length * width) + (z * width) + x + int y = index / (width * length); + int z = (index % (width * length)) / width; + int x = (index % (width * length)) % width; + BlockState state = palette.get(value); + BlockVector3 pt = BlockVector3.at(x, y, z); + try { + if (tileEntitiesMap.containsKey(pt)) { + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), state.toBaseBlock(new CompoundTag(tileEntitiesMap.get(pt)))); + } else { + clipboard.setBlock(clipboard.getMinimumPoint().add(pt), state); + } + } catch (WorldEditException e) { + throw new IOException("Failed to load a block in the schematic"); + } + + index++; + } + + return clipboard; + } + @Override public void close() throws IOException { inputStream.close();