diff --git a/SpigotCore/SpigotCore_21/src/de/steamwar/core/ChaosSchemReader.java b/SpigotCore/SpigotCore_21/src/de/steamwar/core/ChaosSchemReader.java new file mode 100644 index 00000000..8aab6440 --- /dev/null +++ b/SpigotCore/SpigotCore_21/src/de/steamwar/core/ChaosSchemReader.java @@ -0,0 +1,261 @@ +/* + * This file is a part of the SteamWar software. + * + * Copyright (C) 2025 SteamWar.de-Serverteam + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package de.steamwar.core; + +import com.sk89q.worldedit.WorldEdit; +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.ClipboardReader; +import com.sk89q.worldedit.math.BlockVector3; +import com.sk89q.worldedit.regions.CuboidRegion; +import com.sk89q.worldedit.regions.Region; +import com.sk89q.worldedit.world.DataFixer; + +import java.io.DataInputStream; +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +public class ChaosSchemReader implements ClipboardReader { + + private final DataInputStream stream; + + public ChaosSchemReader(DataInputStream stream) { + this.stream = stream; + } + + @Override + public Clipboard read() throws IOException { + byte tag = stream.readByte(); + + if (tag != 0x0A) error("Invalid NBT"); + + String name = stream.readUTF(); + + if (name.isEmpty()) { + tag = stream.readByte(); + if (tag != 0x0A) error("Invalid NBT"); + + stream.readUTF(); + + readSchemTag(); + + stream.readByte(); + } else { + readSchemTag(); + } + } + + private int version; + private int dataVersion; + private DataFixer fixer; + + private int width; + private int height; + private int length; + + private int[] offset = new int[3]; + + private Map palette = new HashMap<>(); + private int[] blocks; + + private Map metadata; + + private Clipboard constructSchematic() { + BlockVector3 min = BlockVector3.at(offset[0], offset[1], offset[2]); + + Region region = new CuboidRegion(min); + + BlockArrayClipboard clipboard = new BlockArrayClipboard(region); + } + + private void readSchemTag() throws IOException { + parseCompoundTag((type, name) -> { + switch (name) { + case "Version" -> version = stream.readInt(); + case "DataVersion" -> { + dataVersion = stream.readInt(); + + Platform platform = WorldEdit.getInstance().getPlatformManager() + .queryCapability(Capability.WORLD_EDITING); + + if (dataVersion < platform.getDataVersion()) { + this.fixer = platform.getDataFixer(); + } + } + case "Metadata" -> metadata = (Map) readByType((byte) 0x0A); + case "Width" -> width = stream.readUnsignedShort(); + case "Height" -> height = stream.readUnsignedShort(); + case "Length" -> length = stream.readUnsignedShort(); + case "Offset" -> offset = readIntArray(); + case "PaletteMax" -> stream.skipNBytes(4); + case "Palette" -> readPalette(); + case "BlockData" -> readBlockData(); + case "Blocks" -> readBlockContainer(); + + default -> skipByType(type); + } + }); + } + + private void readBlockContainer() throws IOException { + parseCompoundTag((type, name) -> { + switch (name) { + case "Palette" -> readPalette(); + case "Data" -> readBlockData(); + + default -> skipByType(type); + } + }); + } + + private void readPalette() throws IOException { + parseCompoundTag((aByte, string) -> palette.put(string, stream.readInt())); + } + + private static final int SEGMENT_BITS = 0x7F; + private static final int CONTINUE_BIT = 0x80; + + private void readBlockData() throws IOException { + byte[] data = readByteArray(); + + int[] blocks = new int[data.length]; + + int x = 0; + int position = 0; + + for (byte current : data) { + blocks[x] |= (current & SEGMENT_BITS) << position; + + if ((current & CONTINUE_BIT) == 0) { + x++; + position = 0; + } else { + position += 7; + + if (position >= 32) throw new RuntimeException("VarInt is too big"); + } + } + + this.blocks = blocks; + } + + private void parseCompoundTag(CompoundReader tag) throws IOException { + byte type; + while ((type = stream.readByte()) != 0x00) { + String name = stream.readUTF(); + + tag.read(type, name); + } + } + + private int[] readIntArray() throws IOException { + int length = stream.readInt(); + int[] arr = new int[length]; + + for (int i = 0; i < length; i++) { + arr[i] = stream.readInt(); + } + + return arr; + } + + private byte[] readByteArray() throws IOException { + return stream.readNBytes(stream.readInt()); + } + + private void skipByType(byte type, String str) throws IOException { + skipByType(type); + } + + private void skipByType(byte type) throws IOException { + switch (type) { + case 0x00 -> stream.skipNBytes(0); + case 0x01 -> stream.skipNBytes(1); + case 0x02 -> stream.skipNBytes(2); + case 0x03, 0x05 -> stream.skipNBytes(4); + case 0x04, 0x06 -> stream.skipNBytes(8); + case 0x07 -> stream.skipNBytes(stream.readInt()); + case 0x08 -> stream.readUTF(); + case 0x09 -> { + byte t = stream.readByte(); + int length = stream.readInt(); + for (int i = 0; i < length; i++) { skipByType(t); } + } + case 0x0A -> parseCompoundTag(this::skipByType); + case 0x0B -> stream.skipNBytes(4L * stream.readInt()); + case 0x0C -> stream.skipNBytes(8L * stream.readInt()); + default -> error("Invalid Type"); + } + } + + private Object readByType(byte type) throws IOException { + return switch (type) { + case 0x00 -> null; + case 0x01 -> stream.readByte(); + case 0x02 -> stream.readShort(); + case 0x03 -> stream.readInt(); + case 0x04 -> stream.readLong(); + case 0x05 -> stream.readFloat(); + case 0x06 -> stream.readDouble(); + case 0x07 -> stream.readNBytes(stream.readInt()); + case 0x08 -> stream.readUTF(); + case 0x09 -> readArray(); + case 0x0A -> readCompound(); + case 0x0B -> stream.skipNBytes(4L * stream.readInt()); + case 0x0C -> stream.skipNBytes(8L * stream.readInt()); + default -> error("Invalid Type"); + }; + } + + private Object[] readArray() throws IOException { + byte t = stream.readByte(); + int length = stream.readInt(); + Object[] obj = new Object[length]; + for (int i = 0; i < length; i++) { + obj[i] = readByType(t); + } + + return obj; + } + + private Map readCompound() throws IOException { + Map entries = new HashMap<>(); + + parseCompoundTag((type, name) -> entries.put(name, readByType(type))); + + return entries; + } + + private void error(String message) { + throw new IllegalStateException("Could not read Schem: " + message); + } + + @Override + public void close() throws IOException { + stream.close(); + } + + private interface CompoundReader { + void read(byte type, String name) throws IOException; + } +}