All Downloads are FREE. Search and download functionalities are using the official Maven repository.

cn.nukkit.level.format.anvil.RegionLoader Maven / Gradle / Ivy

There is a newer version: 1.20.40-r1
Show newest version
package cn.nukkit.level.format.anvil;

import cn.nukkit.Server;
import cn.nukkit.api.PowerNukkitXOnly;
import cn.nukkit.api.Since;
import cn.nukkit.blockstate.BlockState;
import cn.nukkit.level.Level;
import cn.nukkit.level.format.FullChunk;
import cn.nukkit.level.format.LevelProvider;
import cn.nukkit.level.format.generic.BaseRegionLoader;
import cn.nukkit.utils.Binary;
import cn.nukkit.utils.BinaryStream;
import cn.nukkit.utils.ChunkException;
import cn.nukkit.utils.Zlib;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.longs.LongArraySet;
import it.unimi.dsi.fastutil.longs.LongSet;
import it.unimi.dsi.fastutil.longs.LongSets;
import lombok.extern.log4j.Log4j2;

import java.io.EOFException;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.Map;
import java.util.TreeMap;

/**
 * @author MagicDroidX (Nukkit Project)
 */
@Log4j2
public class RegionLoader extends BaseRegionLoader {
    /**
     * 记录已经被更新过的旧区块,默认情况下应该保持null
     */
    @PowerNukkitXOnly
    @Since("1.6.0.0-PNX")
    private static LongSet chunkUpdated = null;

    public RegionLoader(LevelProvider level, int regionX, int regionZ) throws IOException {
        super(level, regionX, regionZ, "mca");
    }

    protected static int getChunkOffset(int x, int z) {
        return x | (z << 5);
    }

    @Override
    protected boolean isChunkGenerated(int index) {
        int[] array = this.primitiveLocationTable.get(index);
        return !(array[0] == 0 || array[1] == 0);
    }

    @Override
    public Chunk readChunk(int x, int z) throws IOException {
        int index = getChunkOffset(x, z);
        if (index < 0 || index >= 4096) {
            return null;
        }

        this.lastUsed = System.currentTimeMillis();

        if (!this.isChunkGenerated(index)) {
            return null;
        }

        try {
            int[] table = this.primitiveLocationTable.get(index);
            RandomAccessFile raf = this.getRandomAccessFile();
            raf.seek((long) table[0] << 12L);
            int length = raf.readInt();
            byte compression = raf.readByte();
            if (length <= 0 || length >= Server.getInstance().getMaximumSizePerChunk()) {
                if (length >= Server.getInstance().getMaximumSizePerChunk()) {
                    table[0] = ++this.lastSector;
                    table[1] = 1;
                    this.primitiveLocationTable.put(index, table);
                    log.error("Corrupted chunk header detected");
                }
                return null;
            }

            if (length > (table[1] << 12)) {
                log.error("Corrupted bigger chunk detected");
                table[1] = length >> 12;
                this.primitiveLocationTable.put(index, table);
                this.writeLocationIndex(index);
            } else if (compression != COMPRESSION_ZLIB && compression != COMPRESSION_GZIP) {
                log.error("Invalid compression type");
                return null;
            }

            byte[] data = new byte[length - 1];
            raf.readFully(data);
            Chunk chunk = this.unserializeChunk(data);
            if (chunk != null) {
                //更新256世界到384世界
                if (levelProvider != null && !chunk.isNew384World && levelProvider.isOverWorld() && levelProvider instanceof Anvil) {
                    //检查重复更新情况
                    if (chunkUpdated == null) {
                        chunkUpdated = LongSets.synchronize(new LongArraySet());
                    }
                    final long chunkHash = Level.chunkHash(chunk.getX(), chunk.getZ());
                    if (!chunkUpdated.contains(chunkHash)) {
                        chunkUpdated.add(chunkHash);
                        chunk.isNew384World = true; //这可以在大部分情况下避免区块重复更新,但是对多线程造成的重复更新仍然无效,所以需要一个set来检查
                        var bid = Server.getInstance().addBusying(System.currentTimeMillis());
                        log.info(Server.getInstance().getLanguage().translateString("nukkit.anvil.converter.update-chunk", levelProvider.getLevel().getName(), chunk.getX() << 4, chunk.getZ() << 4));
                        for (int dx = 0; dx < 16; dx++) {
                            for (int dz = 0; dz < 16; dz++) {
                                for (int dy = 255; dy >= -64; --dy) {
                                    chunk.setBlockState(dx, dy + 64, dz, chunk.getBlockState(dx, dy, dz));
                                    chunk.setBlockStateAtLayer(dx, dy + 64, dz, 1, chunk.getBlockState(dx, dy, dz, 1));
                                    chunk.setBlockState(dx, dy, dz, BlockState.AIR);
                                    chunk.setBlockStateAtLayer(dx, dy, dz, 1, BlockState.AIR);
                                }
                            }
                        }
                        Server.getInstance().removeBusying(bid);
                    }
                }
                return chunk;
            } else {
                log.error("Corrupted chunk detected at ({}, {}) in {}", x, z, levelProvider.getName());
                return null;
            }
        } catch (EOFException e) {
            log.error("Your world is corrupt, because some code is bad and corrupted it. oops. ");
            return null;
        }
    }

    @Override
    protected Chunk unserializeChunk(byte[] data) {
        return Chunk.fromBinary(data, this.levelProvider);
    }

    @Override
    public boolean chunkExists(int x, int z) {
        return this.isChunkGenerated(getChunkOffset(x, z));
    }

    @Override
    protected void saveChunk(int x, int z, byte[] chunkData) throws IOException {
        int length = chunkData.length + 1;
        if (length + 4 > Server.getInstance().getMaximumSizePerChunk()) {
            throw new ChunkException("Chunk is too big! " + (length + 4) + " > " + Server.getInstance().getMaximumSizePerChunk());
        }
        int sectors = (int) Math.ceil((length + 4) / 4096d);
        int index = getChunkOffset(x, z);
        boolean indexChanged = false;
        int[] table = this.primitiveLocationTable.get(index);

        if (table[1] < sectors) {
            table[0] = this.lastSector + 1;
            this.primitiveLocationTable.put(index, table);
            this.lastSector += sectors;
            indexChanged = true;
        } else if (table[1] != sectors) {
            indexChanged = true;
        }

        table[1] = sectors;
        table[2] = (int) (System.currentTimeMillis() / 1000d);

        this.primitiveLocationTable.put(index, table);
        RandomAccessFile raf = this.getRandomAccessFile();
        raf.seek((long) table[0] << 12L);

        BinaryStream stream = new BinaryStream();
        stream.put(Binary.writeInt(length));
        stream.putByte(COMPRESSION_ZLIB);
        stream.put(chunkData);
        byte[] data = stream.getBuffer();
        if (data.length < sectors << 12) {
            byte[] newData = new byte[sectors << 12];
            System.arraycopy(data, 0, newData, 0, data.length);
            data = newData;
        }

        raf.write(data);

        if (indexChanged) {
            this.writeLocationIndex(index);
        }
    }

    @Override
    public void removeChunk(int x, int z) {
        int index = getChunkOffset(x, z);
        int[] table = this.primitiveLocationTable.get(0);
        table[0] = 0;
        table[1] = 0;
        this.primitiveLocationTable.put(index, table);
    }

    @Override
    public void writeChunk(FullChunk chunk) throws Exception {
        this.lastUsed = System.currentTimeMillis();
        byte[] chunkData = chunk.toBinary();
        this.saveChunk(chunk.getX() & 0x1f, chunk.getZ() & 0x1f, chunkData);
    }

    @Override
    public void close() throws IOException {
        this.writeLocationTable();
        this.levelProvider = null;
        super.close();
    }

    @Override
    public int doSlowCleanUp() throws Exception {
        RandomAccessFile raf = this.getRandomAccessFile();
        for (int i = 0; i < 1024; i++) {
            int[] table = this.primitiveLocationTable.get(i);
            if (table[0] == 0 || table[1] == 0) {
                continue;
            }
            raf.seek((long) table[0] << 12L);
            byte[] chunk = new byte[table[1] << 12];
            raf.readFully(chunk);
            int length = Binary.readInt(Arrays.copyOfRange(chunk, 0, 3));
            if (length <= 1) {
                this.primitiveLocationTable.put(i, (table = new int[]{0, 0, 0}));
            }
            try {
                chunk = Zlib.inflate(Arrays.copyOf(chunk, 5));
            } catch (Exception e) {
                this.primitiveLocationTable.put(i, new int[]{0, 0, 0});
                continue;
            }
            chunk = Zlib.deflate(chunk, 9);
            ByteBuffer buffer = ByteBuffer.allocate(4 + 1 + chunk.length);
            buffer.put(Binary.writeInt(chunk.length + 1));
            buffer.put(COMPRESSION_ZLIB);
            buffer.put(chunk);
            chunk = buffer.array();
            int sectors = (int) Math.ceil(chunk.length / 4096d);
            if (sectors > table[1]) {
                table[0] = this.lastSector + 1;
                this.lastSector += sectors;
                this.primitiveLocationTable.put(i, table);
            }
            raf.seek((long) table[0] << 12L);
            byte[] bytes = new byte[sectors << 12];
            ByteBuffer buffer1 = ByteBuffer.wrap(bytes);
            buffer1.put(chunk);
            raf.write(buffer1.array());
        }
        this.writeLocationTable();
        int n = this.cleanGarbage();
        this.writeLocationTable();
        return n;
    }

    @Override
    protected void loadLocationTable() throws IOException {
        RandomAccessFile raf = this.getRandomAccessFile();
        raf.seek(0);
        this.lastSector = 1;
        int[] data = new int[1024 * 2]; //1024 records * 2 times
        for (int i = 0; i < 1024 * 2; i++) {
            data[i] = raf.readInt();
        }
        for (int i = 0; i < 1024; ++i) {
            int index = data[i];
            this.primitiveLocationTable.put(i, new int[]{index >> 8, index & 0xff, data[1024 + i]});
            int value = this.primitiveLocationTable.get(i)[0] + this.primitiveLocationTable.get(i)[1] - 1;
            if (value > this.lastSector) {
                this.lastSector = value;
            }
        }
    }

    private void writeLocationTable() throws IOException {
        RandomAccessFile raf = this.getRandomAccessFile();
        raf.seek(0);
        for (int i = 0; i < 1024; ++i) {
            int[] array = this.primitiveLocationTable.get(i);
            raf.writeInt((array[0] << 8) | array[1]);
        }
        for (int i = 0; i < 1024; ++i) {
            int[] array = this.primitiveLocationTable.get(i);
            raf.writeInt(array[2]);
        }
    }

    private int cleanGarbage() throws IOException {
        RandomAccessFile raf = this.getRandomAccessFile();
        Map sectors = new TreeMap<>();
        for (Int2ObjectMap.Entry entry : this.primitiveLocationTable.int2ObjectEntrySet()) {
            int index = entry.getIntKey();
            int[] data = entry.getValue();
            if (data[0] == 0 || data[1] == 0) {
                this.primitiveLocationTable.put(index, new int[]{0, 0, 0});
                continue;
            }
            sectors.put(data[0], index);
        }

        if (sectors.size() == (this.lastSector - 2)) {
            return 0;
        }
        int shift = 0;
        int lastSector = 1;

        raf.seek(8192);
        int s = 2;
        for (int sector : sectors.keySet()) {
            s = sector;
            int index = sectors.get(sector);
            if ((sector - lastSector) > 1) {
                shift += sector - lastSector - 1;
            }
            if (shift > 0) {
                raf.seek((long) sector << 12L);
                byte[] old = new byte[4096];
                raf.readFully(old);
                raf.seek((long) (sector - shift) << 12L);
                raf.write(old);
            }
            int[] v = this.primitiveLocationTable.get(index);
            v[0] -= shift;
            this.primitiveLocationTable.put(index, v);
            this.lastSector = sector;
        }
        raf.setLength((long) (s + 1) << 12);
        return shift;
    }

    @Override
    protected void writeLocationIndex(int index) throws IOException {
        RandomAccessFile raf = this.getRandomAccessFile();
        int[] array = this.primitiveLocationTable.get(index);
        raf.seek((long) index << 2);
        raf.writeInt((array[0] << 8) | array[1]);
        raf.seek(4096 + ((long) index << 2));
        raf.writeInt(array[2]);
    }

    @Override
    protected void createBlank() throws IOException {
        RandomAccessFile raf = this.getRandomAccessFile();
        raf.seek(0);
        raf.setLength(0);
        this.lastSector = 1;
        int time = (int) (System.currentTimeMillis() / 1000d);
        for (int i = 0; i < 1024; ++i) {
            this.primitiveLocationTable.put(i, new int[]{0, 0, time});
            raf.writeInt(0);
        }
        for (int i = 0; i < 1024; ++i) {
            raf.writeInt(time);
        }
    }

    @Override
    public int getX() {
        return x;
    }

    @Override
    public int getZ() {
        return z;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy