mstefarov / fNbt

A C# library for reading and writing Named Binary Tag (NBT) files and streams. A continuation of libnbt project, originally by Erik Davidson (aphistic).
116 stars 31 forks source link

Unable to create a new region #21

Closed Acedayz closed 8 years ago

Acedayz commented 8 years ago

Hi, I've been trying to create a region (.mca) file from scratch, but I just can't get it to work. I'm using "NBTExplorer" to read the file I'm creating for testing.

When I load the file in NBTExplorer, I can see the chunk that I'm creating but not any tags inside it. So it seems that everything works fine, except the NBT part.

I'd appreciate any help with this issue. Here's the class to create a region file:

public class RegionFile
    {
        private const int SECTOR_BYTES = 4096;
        private const int SECTOR_INTS = SECTOR_BYTES / 4;
        private const int CHUNK_HEADER_SIZE = 5;
        private const int VERSION_DEFLATE = 2;

    private static byte[] emptySector = new byte[SECTOR_BYTES];
    private static int[] offsets;
    private static int[] timestamps;

    private List<bool> sectorFree;

    private static readonly DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);

    public RegionFile(string path)
    {
        offsets = new int[SECTOR_INTS];
        timestamps = new int[SECTOR_INTS];

        try
        {
            using (BinaryWriter file = new BinaryWriter(File.Open(path, FileMode.Create)))
            {
                if (file.BaseStream.Length < SECTOR_BYTES)
                {
                    /* we need to write the chunk offset table */
                    for (int i = 0; i < SECTOR_INTS; ++i)
                    {
                        file.Write((int)0);
                    }
                    // write another sector for the timestamp info
                    for (int i = 0; i < SECTOR_INTS; ++i)
                    {
                        file.Write((int)0);
                    }
                }

                if ((file.BaseStream.Length & 0xfff) != 0)
                {
                    /* the file size is not a multiple of 4KB, grow it */
                    for (int i = 0; i < (file.BaseStream.Length & 0xfff); ++i)
                    {
                        file.Write((byte)0);
                    }
                }
            }

            using (BinaryReader file = new BinaryReader(File.Open(path, FileMode.Open)))
            {
                /* set up the available sector map */
                int nSectors = (int)file.BaseStream.Length / SECTOR_BYTES;
                sectorFree = new List<bool>(nSectors);

                for (int i = 0; i < nSectors; ++i)
                {
                    sectorFree.Add(true);
                }

                sectorFree[0] = false; // chunk offset table
                sectorFree[1] = false; // for the last modified info

                file.BaseStream.Position = 0;
                for (int i = 0; i < SECTOR_INTS; ++i)
                {
                    int offset = file.ReadInt32();
                    offsets[i] = offset;
                    if (offset != 0 && (offset >> 8) + (offset & 0xFF) <= sectorFree.Count())
                    {
                        for (int sectorNum = 0; sectorNum < (offset & 0xFF); ++sectorNum)
                        {
                            sectorFree[(offset >> 8) + sectorNum] = false;
                        }
                    }
                }
                for (int i = 0; i < SECTOR_INTS; ++i)
                {
                    int lastModValue = file.ReadInt32();
                    timestamps[i] = lastModValue;
                }
            }
        }
        catch (IOException e)
        {
            MessageBox.Show(e.ToString());
        }

        // Nbt Arrays
        byte[] biomeBytes = new byte[256];
        for (int i = 0; i < 256; ++i)
        {
            biomeBytes[i] = 0;
        }

        int[] heightMapInts = new int[256];
        for (int i = 0; i < 256; ++i)
        {
            heightMapInts[i] = 0;
        }

        byte[] blockBytes = new byte[4096];
        for (int i = 0; i < 4096; ++i)
        {
            blockBytes[i] = 56;
        }

        byte[] dataBytes = new byte[2048];
        for (int i = 0; i < 2048; ++i)
        {
            dataBytes[i] = 0;
        }

        byte[] blockLightBytes = new byte[2048];
        for (int i = 0; i < 2048; ++i)
        {
            blockLightBytes[i] = 0;
        }

        byte[] skyLightBytes = new byte[2048];
        for (int i = 0; i < 2048; ++i)
        {
            skyLightBytes[i] = 0;
        }

        NbtCompound chunk = new NbtCompound("Chunk") {
            new NbtInt("DataVersion", 176),
            new NbtCompound("Level") {
                new NbtInt("xPos", 0),
                new NbtInt("zPos", 0),
                new NbtLong("LastUpdate", 0),
                new NbtByte("LightPopulated", 0),
                new NbtByte("TerrainPopulated", 1),
                new NbtByte("V", 1),
                new NbtLong("InhabitedTime", 0),
                new NbtByteArray("Biomes", biomeBytes),
                new NbtIntArray("HeightMap", heightMapInts),

                new NbtList("Sections", NbtTagType.Compound)
                {
                    new NbtCompound()
                    {
                        new NbtByte("Y", 0),
                        new NbtByteArray("Blocks", blockBytes),
                        new NbtByteArray("Data", dataBytes),
                        new NbtByteArray("BlockLight", blockLightBytes),
                        new NbtByteArray("SkyLight", skyLightBytes)
                    },
                    new NbtCompound()
                    {
                        new NbtByte("Y", 1),
                        new NbtByteArray("Blocks", blockBytes),
                        new NbtByteArray("Data", dataBytes),
                        new NbtByteArray("BlockLight", blockLightBytes),
                        new NbtByteArray("SkyLight", skyLightBytes)
                    },
                    new NbtCompound()
                    {
                        new NbtByte("Y", 2),
                        new NbtByteArray("Blocks", blockBytes),
                        new NbtByteArray("Data", dataBytes),
                        new NbtByteArray("BlockLight", blockLightBytes),
                        new NbtByteArray("SkyLight", skyLightBytes)
                    }
                },

                new NbtList("Entities", NbtTagType.Compound) {

                },

                new NbtList("TileEntities", NbtTagType.Compound) {

                }
            }
        };

        NbtFile nbtFile = new NbtFile(chunk);
        byte[] data = nbtFile.SaveToBuffer(NbtCompression.ZLib);
        using (BinaryWriter file = new BinaryWriter(new FileStream(path, FileMode.Open)))
        {
            WriteChunk(file, 0, 0, data, data.Length);
        }
    }

    void WriteChunk(BinaryWriter file, int x, int z, byte[] data, int length)
    {
        try
        {
            int offset = getOffset(x, z);
            int sectorNumber = offset >> 8;
            int sectorsAllocated = offset & 0xFF;
            int sectorsNeeded = (length + CHUNK_HEADER_SIZE) / SECTOR_BYTES + 1;

            // Maximum chunk size is 1MB
            if (sectorsNeeded >= 256)
            {
                return;
            }

            if (sectorNumber != 0 && sectorsAllocated == sectorsNeeded)
            {
                WriteChunk(file, sectorNumber, data, length);
            }
            else
            {
                for (int i = 0; i < sectorsAllocated; ++i)
                {
                    sectorFree[sectorNumber + i] = true;
                }

                int runStart = sectorFree.IndexOf(true);
                int runLength = 0;
                if (runStart != -1)
                {
                    for (int i = runStart; i < sectorFree.Count(); ++i)
                    {
                        if (runLength != 0)
                        {
                            if (sectorFree[i])
                                runLength++;
                            else
                                runLength = 0;
                        }
                        else if (sectorFree[i])
                        {
                            runStart = i;
                            runLength = 1;
                        }
                        if (runLength >= sectorsNeeded)
                        {
                            break;
                        }
                    }
                }

                if (runLength >= sectorsNeeded)
                {
                    /* We found a free space large enough */
                    sectorNumber = runStart;
                    SetOffset(file, x, z, (sectorNumber << 8) | sectorsNeeded);
                    for (int i = 0; i < sectorsNeeded; ++i)
                    {
                        sectorFree[sectorNumber + i] = false;
                    }
                    WriteChunk(file, sectorNumber, data, length);
                }
                else {
                    // No free space large enough found; we need to grow the file

                    file.BaseStream.Position = (int)file.BaseStream.Length;
                    sectorNumber = sectorFree.Count;
                    for (int i = 0; i < sectorsNeeded; ++i)
                    {
                        file.Write(emptySector);
                        sectorFree.Add(false);
                    }

                    WriteChunk(file, sectorNumber, data, length);
                    SetOffset(file, x, z, (sectorNumber << 8) | sectorsNeeded);
                }

                SetTimestamp(file, x, z, CurrentTimeMillis());
            }
        }
        catch (IOException e)
        {
            MessageBox.Show(e.ToString());
        }
    }

    private void WriteChunk(BinaryWriter file, int sectorNumber, byte[] data, int length)
    {
        file.BaseStream.Position = sectorNumber * SECTOR_BYTES;
        file.Write(length + 1); // Chunk length
        file.Write((byte)VERSION_DEFLATE); // Chunk version number
        file.Write(data, 0, length);
    }

    int getOffset(int x, int z)
    {
        return offsets[x + z * 32];
    }

    void SetOffset(BinaryWriter file, int x, int z, int offset)
    {
        offsets[x + z * 32] = offset;
        file.BaseStream.Position = (x + z * 32) * 4;
        file.Write(offset);
    }

    void SetTimestamp(BinaryWriter file, int x, int z, int value)
    {
        timestamps[x + z * 32] = value;
        file.BaseStream.Position = SECTOR_BYTES + (x + z * 32) * 4;
        file.Write(value);
    }

    public static int CurrentTimeMillis()
    {
        return (int)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
    }
}
mstefarov commented 8 years ago

Thanks for reporting the issue! I'm investigating, and I'll let you know what I find shortly.